diff --git a/.config/suppress.json b/.config/suppress.json index 55e607b1b0c..9be220b291e 100644 --- a/.config/suppress.json +++ b/.config/suppress.json @@ -1,17 +1,17 @@ { - "tool": "Credential Scanner", - "suppressions": [ - { - "file": "\\test\\tools\\Modules\\WebListener\\ClientCert.pfx", - "_justification": "Test certificate with private key" - }, - { - "file": "\\test\\tools\\Modules\\WebListener\\ServerCert.pfx", - "_justification": "Test certificate with private key" - }, - { - "file": "\\test\\powershell\\Modules\\Microsoft.PowerShell.Security\\certificateCommon.psm1", - "_justification": "Test certificate with private key and inline suppression isn't working" - } - ] + "tool": "Credential Scanner", + "suppressions": [ + { + "file": "\\test\\tools\\Modules\\WebListener\\ClientCert.pfx", + "_justification": "Test certificate with private key" + }, + { + "file": "\\test\\tools\\Modules\\WebListener\\ServerCert.pfx", + "_justification": "Test certificate with private key" + }, + { + "file": "\\test\\powershell\\Modules\\Microsoft.PowerShell.Security\\certificateCommon.psm1", + "_justification": "Test certificate with private key and inline suppression isn't working" + } + ] } diff --git a/.editorconfig b/.editorconfig index 72707109516..57d2f6c6c3e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -126,6 +126,10 @@ dotnet_code_quality_unused_parameters = non_public:suggestion # https://learn.microsoft.com/en-gb/dotnet/fundamentals/code-analysis/quality-rules/ca1859 dotnet_diagnostic.CA1859.severity = suggestion +# Disable SA1600 (ElementsMustBeDocumented) for test directory only +[test/**/*.cs] +dotnet_diagnostic.SA1600.severity = none + # CSharp code style settings: [*.cs] diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6de13fd8daf..14569b81924 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -3,6 +3,9 @@ # Areas are not limited to the filters defined in this file # First, let's start with areas with no filters or paths +# Default +* @PowerShell/powershell-maintainers + # Area: Performance # @adityapatwardhan diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 776a9a8c60f..35eab8c9b5b 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -91,7 +91,7 @@ To run the link-checker, follow these steps: 1. Issues marked as [`First-Time-Issue`][first-time-issue], are identified as being easy and a great way to learn about this project and making contributions. - + ### Finding or creating an issue 1. Follow the instructions in [Contributing to Issues][contribute-issues] to find or open an issue. @@ -264,18 +264,10 @@ Please see PowerShell [Testing Guidelines - Running Tests Outside of CI][running * Our CI contains automated spell checking and link checking for Markdown files. If there is any false-positive, [run the spell checker command-line tool in interactive mode](#spell-checking-documentation) to add words to the `.spelling` file. -* Our packaging test may not pass and ask you to update `files.wxs` file if you add/remove/update nuget package references or add/remove assert files. - - You could update the file manually in accordance with messages in the test log file. Or you can use automatically generated file. To get the file you should build the msi package locally: - - ```powershell - Import-Module .\build.psm1 - Start-PSBuild -Clean -CrossGen -PSModuleRestore -Runtime win7-x64 -Configuration Release -ReleaseTag - Import-Module .\tools\packaging - Start-PSPackage -Type msi -ReleaseTag -WindowsRuntime 'win7-x64' -SkipReleaseChecks - ``` - Last command will report where new file is located. + You could update the `.spelling` file manually in accordance with messages in the test log file, or + [run the spell checker command-line tool in interactive mode](#spell-checking-documentation) + to add the false-positive words directly. #### Pull Request - Workflow diff --git a/.github/ISSUE_TEMPLATE/WG_member_request.yaml b/.github/ISSUE_TEMPLATE/WG_member_request.yaml index 052a2b0f713..1d7f0e9ba53 100644 --- a/.github/ISSUE_TEMPLATE/WG_member_request.yaml +++ b/.github/ISSUE_TEMPLATE/WG_member_request.yaml @@ -64,4 +64,3 @@ body: I have the following public links to articles, code, or other resources that demonstrate my skills... validations: required: true - diff --git a/.github/SECURITY.md b/.github/SECURITY.md index f941d308b1f..797f7003851 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -12,9 +12,7 @@ If you believe you have found a security vulnerability in any Microsoft-owned re Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/security.md/msrc/create-report). -If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/security.md/msrc/pgp). - -You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). +You should receive a response within 24 hours. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: diff --git a/.github/actions/build/ci/action.yml b/.github/actions/build/ci/action.yml index 93adaf6b17a..65331fb3185 100644 --- a/.github/actions/build/ci/action.yml +++ b/.github/actions/build/ci/action.yml @@ -5,13 +5,15 @@ runs: steps: - name: Capture Environment if: success() || failure() - run: 'Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose' + run: |- + Import-Module .\tools\ci.psm1 + Show-Environment shell: pwsh - name: Set Build Name for Non-PR if: github.event_name != 'PullRequest' run: Write-Host "##vso[build.updatebuildnumber]$env:BUILD_SOURCEBRANCHNAME-$env:BUILD_SOURCEVERSION-$((get-date).ToString("yyyyMMddhhmmss"))" shell: pwsh - - uses: actions/setup-dotnet@v4 + - uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1 with: global-json-file: ./global.json - name: Bootstrap @@ -31,22 +33,8 @@ runs: Import-Module .\tools\ci.psm1 Invoke-CIBuild shell: pwsh - - name: xUnit Tests - if: success() - continue-on-error: true - run: |- - Write-Verbose -Verbose "Running xUnit tests..." - Import-Module .\tools\ci.psm1 - Restore-PSOptions - Invoke-CIxUnit -SkipFailing - shell: pwsh - name: Upload build artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: build path: ${{ runner.workspace }}/build - - name: Upload xunit artifact - uses: actions/upload-artifact@v4 - with: - name: testResults-xunit - path: ${{ runner.workspace }}/xunit diff --git a/.github/actions/infrastructure/get-changed-files/README.md b/.github/actions/infrastructure/get-changed-files/README.md new file mode 100644 index 00000000000..277b28c0674 --- /dev/null +++ b/.github/actions/infrastructure/get-changed-files/README.md @@ -0,0 +1,122 @@ +# Get Changed Files Action + +A reusable composite action that retrieves the list of files changed in a pull request or push event. + +## Features + +- Supports both `pull_request` and `push` events +- Optional filtering by file pattern +- Returns files as JSON array for easy consumption +- Filters out deleted files (only returns added, modified, or renamed files) +- Handles up to 100 changed files per request + +## Usage + +### Basic Usage (Pull Requests Only) + +```yaml +- name: Get changed files + id: changed-files + uses: "./.github/actions/infrastructure/get-changed-files" + +- name: Process files + run: | + echo "Changed files: ${{ steps.changed-files.outputs.files }}" + echo "Count: ${{ steps.changed-files.outputs.count }}" +``` + +### With Filtering + +```yaml +# Get only markdown files +- name: Get changed markdown files + id: changed-md + uses: "./.github/actions/infrastructure/get-changed-files" + with: + filter: '*.md' + +# Get only GitHub workflow/action files +- name: Get changed GitHub files + id: changed-github + uses: "./.github/actions/infrastructure/get-changed-files" + with: + filter: '.github/' +``` + +### Support Both PR and Push Events + +```yaml +- name: Get changed files + id: changed-files + uses: "./.github/actions/infrastructure/get-changed-files" + with: + event-types: 'pull_request,push' +``` + +## Inputs + +| Name | Description | Required | Default | +|------|-------------|----------|---------| +| `filter` | Optional filter pattern (e.g., `*.md` for markdown files, `.github/` for GitHub files) | No | `''` | +| `event-types` | Comma-separated list of event types to support (`pull_request`, `push`) | No | `pull_request` | + +## Outputs + +| Name | Description | +|------|-------------| +| `files` | JSON array of changed file paths | +| `count` | Number of changed files | + +## Filter Patterns + +The action supports simple filter patterns: + +- **Extension matching**: Use `*.ext` to match files with a specific extension + - Example: `*.md` matches all markdown files + - Example: `*.yml` matches all YAML files + +- **Path prefix matching**: Use a path prefix to match files in a directory + - Example: `.github/` matches all files in the `.github` directory + - Example: `tools/` matches all files in the `tools` directory + +## Example: Processing Changed Files + +```yaml +- name: Get changed files + id: changed-files + uses: "./.github/actions/infrastructure/get-changed-files" + +- name: Process each file + shell: pwsh + env: + CHANGED_FILES: ${{ steps.changed-files.outputs.files }} + run: | + $changedFilesJson = $env:CHANGED_FILES + $changedFiles = $changedFilesJson | ConvertFrom-Json + + foreach ($file in $changedFiles) { + Write-Host "Processing: $file" + # Your processing logic here + } +``` + +## Limitations + +- Simple filter patterns only (no complex glob or regex patterns) + +## Pagination + +The action automatically handles pagination to fetch **all** changed files in a PR, regardless of how many files were changed: + +- Fetches files in batches of 100 per page +- Continues fetching until all files are retrieved +- Logs a note when pagination occurs, showing the total file count +- **No file limit** - all changed files will be processed, even in very large PRs + +This ensures that critical workflows (such as merge conflict checking, link validation, etc.) don't miss files due to pagination limits. + +## Related Actions + +- **markdownlinks**: Uses this pattern to get changed markdown files +- **merge-conflict-checker**: Uses this pattern to get changed files for conflict detection +- **path-filters**: Similar functionality but with more complex filtering logic diff --git a/.github/actions/infrastructure/get-changed-files/action.yml b/.github/actions/infrastructure/get-changed-files/action.yml new file mode 100644 index 00000000000..51631cfe141 --- /dev/null +++ b/.github/actions/infrastructure/get-changed-files/action.yml @@ -0,0 +1,117 @@ +name: 'Get Changed Files' +description: 'Gets the list of files changed in a pull request or push event' +inputs: + filter: + description: 'Optional filter pattern (e.g., "*.md" for markdown files, ".github/" for GitHub files)' + required: false + default: '' + event-types: + description: 'Comma-separated list of event types to support (pull_request, push)' + required: false + default: 'pull_request' +outputs: + files: + description: 'JSON array of changed file paths' + value: ${{ steps.get-files.outputs.files }} + count: + description: 'Number of changed files' + value: ${{ steps.get-files.outputs.count }} +runs: + using: 'composite' + steps: + - name: Get changed files + id: get-files + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + with: + script: | + const eventTypes = '${{ inputs.event-types }}'.split(',').map(t => t.trim()); + const filter = '${{ inputs.filter }}'; + let changedFiles = []; + + if (eventTypes.includes('pull_request') && context.eventName === 'pull_request') { + console.log(`Getting files changed in PR #${context.payload.pull_request.number}`); + + // Fetch all files changed in the PR with pagination + let allFiles = []; + let page = 1; + let fetchedCount; + + do { + const { data: files } = await github.rest.pulls.listFiles({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: context.payload.pull_request.number, + per_page: 100, + page: page + }); + + allFiles = allFiles.concat(files); + fetchedCount = files.length; + page++; + } while (fetchedCount === 100); + + if (allFiles.length >= 100) { + console.log(`Note: This PR has ${allFiles.length} changed files. All files fetched using pagination.`); + } + + changedFiles = allFiles + .filter(file => file.status === 'added' || file.status === 'modified' || file.status === 'renamed') + .map(file => file.filename); + + } else if (eventTypes.includes('push') && context.eventName === 'push') { + console.log(`Getting files changed in push to ${context.ref}`); + + const { data: comparison } = await github.rest.repos.compareCommits({ + owner: context.repo.owner, + repo: context.repo.repo, + base: context.payload.before, + head: context.payload.after, + }); + + changedFiles = comparison.files + .filter(file => file.status === 'added' || file.status === 'modified' || file.status === 'renamed') + .map(file => file.filename); + + } else { + core.setFailed(`Unsupported event type: ${context.eventName}. Supported types: ${eventTypes.join(', ')}`); + return; + } + + // Apply filter if provided + if (filter) { + const filterLower = filter.toLowerCase(); + const beforeFilter = changedFiles.length; + changedFiles = changedFiles.filter(file => { + const fileLower = file.toLowerCase(); + // Support simple patterns like "*.md" or ".github/" + if (filterLower.startsWith('*.')) { + const ext = filterLower.substring(1); + return fileLower.endsWith(ext); + } else { + return fileLower.startsWith(filterLower); + } + }); + console.log(`Filter '${filter}' applied: ${beforeFilter} → ${changedFiles.length} files`); + } + + // Calculate simple hash for verification + const crypto = require('crypto'); + const filesJson = JSON.stringify(changedFiles.sort()); + const hash = crypto.createHash('sha256').update(filesJson).digest('hex').substring(0, 8); + + // Log changed files in a collapsible group + core.startGroup(`Changed Files (${changedFiles.length} total, hash: ${hash})`); + if (changedFiles.length > 0) { + changedFiles.forEach(file => console.log(` - ${file}`)); + } else { + console.log(' (no files changed)'); + } + core.endGroup(); + + console.log(`Found ${changedFiles.length} changed files`); + core.setOutput('files', JSON.stringify(changedFiles)); + core.setOutput('count', changedFiles.length); + +branding: + icon: 'file-text' + color: 'blue' diff --git a/.github/actions/infrastructure/markdownlinks/Parse-MarkdownLink.ps1 b/.github/actions/infrastructure/markdownlinks/Parse-MarkdownLink.ps1 new file mode 100644 index 00000000000..a56d696eb6e --- /dev/null +++ b/.github/actions/infrastructure/markdownlinks/Parse-MarkdownLink.ps1 @@ -0,0 +1,182 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +#requires -version 7 +# Markdig is always available in PowerShell 7 +<# +.SYNOPSIS + Parse CHANGELOG files using Markdig to extract links. + +.DESCRIPTION + This script uses Markdig.Markdown.Parse to parse all markdown files in the CHANGELOG directory + and extract different types of links (inline links, reference links, etc.). + +.PARAMETER ChangelogPath + Path to the CHANGELOG directory. Defaults to ./CHANGELOG + +.PARAMETER LinkType + Filter by link type: All, Inline, Reference, AutoLink. Defaults to All. + +.EXAMPLE + .\Parse-MarkdownLink.ps1 + +.EXAMPLE + .\Parse-MarkdownLink.ps1 -LinkType Reference +#> + +param( + [string]$ChangelogPath = "./CHANGELOG", + [ValidateSet("All", "Inline", "Reference", "AutoLink")] + [string]$LinkType = "All" +) + +Write-Verbose "Using built-in Markdig functionality to parse markdown files" + +function Get-LinksFromMarkdownAst { + param( + [Parameter(Mandatory)] + [object]$Node, + [Parameter(Mandatory)] + [string]$FileName, + [System.Collections.ArrayList]$Links + ) + + if ($null -eq $Links) { + return + } + + # Check if current node is a link + if ($Node -is [Markdig.Syntax.Inlines.LinkInline]) { + $linkInfo = [PSCustomObject]@{ + Path = $FileName + Line = $Node.Line + 1 # Convert to 1-based line numbering + Column = $Node.Column + 1 # Convert to 1-based column numbering + Url = $Node.Url ?? "" + Text = $Node.FirstChild?.ToString() ?? "" + Type = "Inline" + IsImage = $Node.IsImage + } + [void]$Links.Add($linkInfo) + } + elseif ($Node -is [Markdig.Syntax.Inlines.AutolinkInline]) { + $linkInfo = [PSCustomObject]@{ + Path = $FileName + Line = $Node.Line + 1 + Column = $Node.Column + 1 + Url = $Node.Url ?? "" + Text = $Node.Url ?? "" + Type = "AutoLink" + IsImage = $false + } + [void]$Links.Add($linkInfo) + } + elseif ($Node -is [Markdig.Syntax.LinkReferenceDefinitionGroup]) { + foreach ($refDef in $Node) { + $linkInfo = [PSCustomObject]@{ + Path = $FileName + Line = $refDef.Line + 1 + Column = $refDef.Column + 1 + Url = $refDef.Url ?? "" + Text = $refDef.Label ?? "" + Type = "Reference" + IsImage = $false + } + [void]$Links.Add($linkInfo) + } + } + elseif ($Node -is [Markdig.Syntax.LinkReferenceDefinition]) { + $linkInfo = [PSCustomObject]@{ + Path = $FileName + Line = $Node.Line + 1 + Column = $Node.Column + 1 + Url = $Node.Url ?? "" + Text = $Node.Label ?? "" + Type = "Reference" + IsImage = $false + } + [void]$Links.Add($linkInfo) + } + + # For MarkdownDocument (root), iterate through all blocks + if ($Node -is [Markdig.Syntax.MarkdownDocument]) { + foreach ($block in $Node) { + Get-LinksFromMarkdownAst -Node $block -FileName $FileName -Links $Links + } + } + # For block containers, iterate through children + elseif ($Node -is [Markdig.Syntax.ContainerBlock]) { + foreach ($child in $Node) { + Get-LinksFromMarkdownAst -Node $child -FileName $FileName -Links $Links + } + } + # For leaf blocks with inlines, process the inline content + elseif ($Node -is [Markdig.Syntax.LeafBlock] -and $Node.Inline) { + Get-LinksFromMarkdownAst -Node $Node.Inline -FileName $FileName -Links $Links + } + # For inline containers, process all child inlines + elseif ($Node -is [Markdig.Syntax.Inlines.ContainerInline]) { + $child = $Node.FirstChild + while ($child) { + Get-LinksFromMarkdownAst -Node $child -FileName $FileName -Links $Links + $child = $child.NextSibling + } + } + # For other inline elements that might have children + elseif ($Node.PSObject.Properties.Name -contains "FirstChild" -and $Node.FirstChild) { + $child = $Node.FirstChild + while ($child) { + Get-LinksFromMarkdownAst -Node $child -FileName $FileName -Links $Links + $child = $child.NextSibling + } + } +} + +function Parse-ChangelogFiles { + param( + [string]$Path + ) + + if (-not (Test-Path $Path)) { + Write-Error "CHANGELOG directory not found: $Path" + return + } + + $markdownFiles = Get-ChildItem -Path $Path -Filter "*.md" -File + + if ($markdownFiles.Count -eq 0) { + Write-Warning "No markdown files found in $Path" + return + } + + $allLinks = [System.Collections.ArrayList]::new() + + foreach ($file in $markdownFiles) { + Write-Verbose "Processing file: $($file.Name)" + + try { + $content = Get-Content -Path $file.FullName -Raw -Encoding UTF8 + + # Parse the markdown content using Markdig + $document = [Markdig.Markdown]::Parse($content, [Markdig.MarkdownPipelineBuilder]::new()) + + # Extract links from the AST + Get-LinksFromMarkdownAst -Node $document -FileName $file.FullName -Links $allLinks + + } catch { + Write-Warning "Error processing file $($file.Name): $($_.Exception.Message)" + } + } + + # Filter by link type if specified + if ($LinkType -ne "All") { + $allLinks = $allLinks | Where-Object { $_.Type -eq $LinkType } + } + + return $allLinks +} + +# Main execution +$links = Parse-ChangelogFiles -Path $ChangelogPath + +# Output PowerShell objects +$links diff --git a/.github/actions/infrastructure/markdownlinks/README.md b/.github/actions/infrastructure/markdownlinks/README.md new file mode 100644 index 00000000000..e566ec2bcc3 --- /dev/null +++ b/.github/actions/infrastructure/markdownlinks/README.md @@ -0,0 +1,177 @@ +# Verify Markdown Links Action + +A GitHub composite action that verifies all links in markdown files using PowerShell and Markdig. + +## Features + +- ✅ Parses markdown files using Markdig (built into PowerShell 7) +- ✅ Extracts all link types: inline links, reference links, and autolinks +- ✅ Verifies HTTP/HTTPS links with configurable timeouts and retries +- ✅ Validates local file references +- ✅ Supports excluding specific URL patterns +- ✅ Provides detailed error reporting with file locations +- ✅ Outputs metrics for CI/CD integration + +## Usage + +### Basic Usage + +```yaml +- name: Verify Markdown Links + uses: ./.github/actions/infrastructure/markdownlinks + with: + path: './CHANGELOG' +``` + +### Advanced Usage + +```yaml +- name: Verify Markdown Links + uses: ./.github/actions/infrastructure/markdownlinks + with: + path: './docs' + fail-on-error: 'true' + timeout: 30 + max-retries: 2 + exclude-patterns: '*.example.com/*,*://localhost/*' +``` + +### With Outputs + +```yaml +- name: Verify Markdown Links + id: verify-links + uses: ./.github/actions/infrastructure/markdownlinks + with: + path: './CHANGELOG' + fail-on-error: 'false' + +- name: Display Results + run: | + echo "Total links: ${{ steps.verify-links.outputs.total-links }}" + echo "Passed: ${{ steps.verify-links.outputs.passed-links }}" + echo "Failed: ${{ steps.verify-links.outputs.failed-links }}" + echo "Skipped: ${{ steps.verify-links.outputs.skipped-links }}" +``` + +## Inputs + +| Input | Description | Required | Default | +|-------|-------------|----------|---------| +| `path` | Path to the directory containing markdown files to verify | No | `./CHANGELOG` | +| `exclude-patterns` | Comma-separated list of URL patterns to exclude from verification | No | `''` | +| `fail-on-error` | Whether to fail the action if any links are broken | No | `true` | +| `timeout` | Timeout in seconds for HTTP requests | No | `30` | +| `max-retries` | Maximum number of retries for failed requests | No | `2` | + +## Outputs + +| Output | Description | +|--------|-------------| +| `total-links` | Total number of unique links checked | +| `passed-links` | Number of links that passed verification | +| `failed-links` | Number of links that failed verification | +| `skipped-links` | Number of links that were skipped | + +## Excluded Link Types + +The action automatically skips the following link types: + +- **Anchor links** (`#section-name`) - Would require full markdown parsing +- **Email links** (`mailto:user@example.com`) - Cannot be verified without sending email + +## GitHub Workflow Test + +This section provides a workflow example and instructions for testing the link verification action. + +### Testing the Workflow + +To test that the workflow properly detects broken links: + +1. Make change to this file (e.g., this README.md file already contains one in the [Broken Link Test](#broken-link-test) section) +1. The workflow will run and should fail, reporting the broken link(s) +1. Revert your change to this file +1. Push again to verify the workflow passes + +### Example Workflow Configuration + +```yaml +name: Verify Links + +on: + push: + branches: [ main ] + paths: + - '**/*.md' + pull_request: + branches: [ main ] + paths: + - '**/*.md' + schedule: + # Run weekly to catch external link rot + - cron: '0 0 * * 0' + +jobs: + verify-links: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Verify CHANGELOG Links + uses: ./.github/actions/infrastructure/markdownlinks + with: + path: './CHANGELOG' + fail-on-error: 'true' + + - name: Verify Documentation Links + uses: ./.github/actions/infrastructure/markdownlinks + with: + path: './docs' + fail-on-error: 'false' + exclude-patterns: '*.internal.example.com/*' +``` + +## How It Works + +1. **Parse Markdown**: Uses `Parse-MarkdownLink.ps1` to extract all links from markdown files using Markdig +2. **Deduplicate**: Groups links by URL to avoid checking the same link multiple times +3. **Verify Links**: + - HTTP/HTTPS links: Makes HEAD/GET requests with configurable timeout and retries + - Local file references: Checks if the file exists relative to the markdown file + - Excluded patterns: Skips links matching the exclude patterns +4. **Report Results**: Displays detailed results with file locations for failed links +5. **Set Outputs**: Provides metrics for downstream steps + +## Error Output Example + +``` +✗ FAILED: https://example.com/broken-link - HTTP 404 + Found in: /path/to/file.md:42:15 + Found in: /path/to/other.md:100:20 + +Link Verification Summary +============================================================ +Total URLs checked: 150 +Passed: 145 +Failed: 2 +Skipped: 3 + +Failed Links: + • https://example.com/broken-link + Error: HTTP 404 + Occurrences: 2 +``` + +## Requirements + +- PowerShell 7+ (includes Markdig) +- Runs on: `ubuntu-latest`, `windows-latest`, `macos-latest` + +## Broken Link Test + +- [Broken Link](https://github.com/PowerShell/PowerShell/wiki/NonExistentPage404) + +## License + +Same as the PowerShell repository. diff --git a/.github/actions/infrastructure/markdownlinks/Verify-MarkdownLinks.ps1 b/.github/actions/infrastructure/markdownlinks/Verify-MarkdownLinks.ps1 new file mode 100644 index 00000000000..f50ab1590b9 --- /dev/null +++ b/.github/actions/infrastructure/markdownlinks/Verify-MarkdownLinks.ps1 @@ -0,0 +1,317 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +#Requires -Version 7.0 + +<# +.SYNOPSIS + Verify all links in markdown files. + +.DESCRIPTION + This script parses markdown files to extract links and verifies their accessibility. + It supports HTTP/HTTPS links and local file references. + +.PARAMETER Path + Path to the directory containing markdown files. Defaults to current directory. + +.PARAMETER File + Array of specific markdown files to verify. If provided, Path parameter is ignored. + +.PARAMETER TimeoutSec + Timeout in seconds for HTTP requests. Defaults to 30. + +.PARAMETER MaximumRetryCount + Maximum number of retries for failed requests. Defaults to 2. + +.PARAMETER RetryIntervalSec + Interval in seconds between retry attempts. Defaults to 2. + +.EXAMPLE + .\Verify-MarkdownLinks.ps1 -Path ./CHANGELOG + +.EXAMPLE + .\Verify-MarkdownLinks.ps1 -Path ./docs -FailOnError + +.EXAMPLE + .\Verify-MarkdownLinks.ps1 -File @('CHANGELOG/7.5.md', 'README.md') +#> + +param( + [Parameter(ParameterSetName = 'ByPath', Mandatory)] + [string]$Path = "Q:\src\git\powershell\docs\git", + [Parameter(ParameterSetName = 'ByFile', Mandatory)] + [string[]]$File = @(), + [int]$TimeoutSec = 30, + [int]$MaximumRetryCount = 2, + [int]$RetryIntervalSec = 2 +) + +$ErrorActionPreference = 'Stop' + +# Get the script directory +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path + +# Determine what to process: specific files or directory +if ($File.Count -gt 0) { + Write-Host "Extracting links from $($File.Count) specified markdown file(s)" -ForegroundColor Cyan + + # Process each file individually + $allLinks = @() + $parseScriptPath = Join-Path $scriptDir "Parse-MarkdownLink.ps1" + + foreach ($filePath in $File) { + if (Test-Path $filePath) { + Write-Verbose "Processing: $filePath" + $fileLinks = & $parseScriptPath -ChangelogPath $filePath + $allLinks += $fileLinks + } + else { + Write-Warning "File not found: $filePath" + } + } +} +else { + Write-Host "Extracting links from markdown files in: $Path" -ForegroundColor Cyan + + # Get all links from markdown files using the Parse-ChangelogLinks script + $parseScriptPath = Join-Path $scriptDir "Parse-MarkdownLink.ps1" + $allLinks = & $parseScriptPath -ChangelogPath $Path +} + +if ($allLinks.Count -eq 0) { + Write-Host "No links found in markdown files." -ForegroundColor Yellow + exit 0 +} + +Write-Host "Found $($allLinks.Count) links to verify" -ForegroundColor Green + +# Group links by URL to avoid duplicate checks +$uniqueLinks = $allLinks | Group-Object -Property Url + +Write-Host "Unique URLs to verify: $($uniqueLinks.Count)" -ForegroundColor Cyan + +$results = @{ + Total = $uniqueLinks.Count + Passed = 0 + Failed = 0 + Skipped = 0 + Errors = [System.Collections.ArrayList]::new() +} + +function Test-HttpLink { + param( + [string]$Url + ) + + try { + # Try HEAD request first (faster, doesn't download content) + $response = Invoke-WebRequest -Uri $Url ` + -Method Head ` + -TimeoutSec $TimeoutSec ` + -MaximumRetryCount $MaximumRetryCount ` + -RetryIntervalSec $RetryIntervalSec ` + -UserAgent "Mozilla/5.0 (compatible; GitHubActions/1.0; +https://github.com/PowerShell/PowerShell)" ` + -SkipHttpErrorCheck + + # If HEAD fails with 404 or 405, retry with GET (some servers don't support HEAD) + if ($response.StatusCode -eq 404 -or $response.StatusCode -eq 405) { + Write-Verbose "HEAD request failed with $($response.StatusCode), retrying with GET for: $Url" + $response = Invoke-WebRequest -Uri $Url ` + -Method Get ` + -TimeoutSec $TimeoutSec ` + -MaximumRetryCount $MaximumRetryCount ` + -RetryIntervalSec $RetryIntervalSec ` + -UserAgent "Mozilla/5.0 (compatible; GitHubActions/1.0; +https://github.com)" ` + -SkipHttpErrorCheck + } + + if ($response.StatusCode -ge 200 -and $response.StatusCode -lt 400) { + return @{ Success = $true; StatusCode = $response.StatusCode } + } + else { + return @{ Success = $false; StatusCode = $response.StatusCode; Error = "HTTP $($response.StatusCode)" } + } + } + catch { + return @{ Success = $false; StatusCode = 0; Error = $_.Exception.Message } + } +} + +function Test-LocalLink { + param( + [string]$Url, + [string]$BasePath + ) + + # Strip query parameters (e.g., ?sanitize=true) and anchors (e.g., #section) + $cleanUrl = $Url -replace '\?.*$', '' -replace '#.*$', '' + + # Handle relative paths + $targetPath = Join-Path $BasePath $cleanUrl + + if (Test-Path $targetPath) { + return @{ Success = $true } + } + else { + return @{ Success = $false; Error = "File not found: $targetPath" } + } +} + +# Verify each unique link +$progressCount = 0 +foreach ($linkGroup in $uniqueLinks) { + $progressCount++ + $url = $linkGroup.Name + $occurrences = $linkGroup.Group + Write-Verbose -Verbose "[$progressCount/$($uniqueLinks.Count)] Checking: $url" + + # Determine link type and verify + $verifyResult = $null + if ($url -match '^https?://') { + $verifyResult = Test-HttpLink -Url $url + } + elseif ($url -match '^#') { + Write-Verbose -Verbose "Skipping anchor link: $url" + $results.Skipped++ + continue + } + elseif ($url -match '^mailto:') { + Write-Verbose -Verbose "Skipping mailto link: $url" + $results.Skipped++ + continue + } + else { + $basePath = Split-Path -Parent $occurrences[0].Path + $verifyResult = Test-LocalLink -Url $url -BasePath $basePath + } + if ($verifyResult.Success) { + Write-Host "✓ OK: $url" -ForegroundColor Green + $results.Passed++ + } + else { + $errorMsg = if ($verifyResult.StatusCode) { + "HTTP $($verifyResult.StatusCode)" + } + else { + $verifyResult.Error + } + + # Determine if this status code should be ignored or treated as failure + # Ignore: 401 (Unauthorized), 403 (Forbidden), 429 (Too Many Requests - already retried) + # Fail: 404 (Not Found), 410 (Gone), 406 (Not Acceptable) - these indicate broken links + $shouldIgnore = $false + $ignoreReason = "" + + switch ($verifyResult.StatusCode) { + 401 { + $shouldIgnore = $true + $ignoreReason = "authentication required" + } + 403 { + $shouldIgnore = $true + $ignoreReason = "access forbidden" + } + 429 { + $shouldIgnore = $true + $ignoreReason = "rate limited (already retried)" + } + } + + if ($shouldIgnore) { + Write-Host "⊘ IGNORED: $url - $errorMsg ($ignoreReason)" -ForegroundColor Yellow + Write-Verbose -Verbose "Ignored error details for $url - Status: $($verifyResult.StatusCode) - $ignoreReason" + foreach ($occurrence in $occurrences) { + Write-Verbose -Verbose " Found in: $($occurrence.Path):$($occurrence.Line):$($occurrence.Column)" + } + $results.Skipped++ + } + else { + Write-Host "✗ FAILED: $url - $errorMsg" -ForegroundColor Red + foreach ($occurrence in $occurrences) { + Write-Host " Found in: $($occurrence.Path):$($occurrence.Line):$($occurrence.Column)" -ForegroundColor DarkGray + } + $results.Failed++ + [void]$results.Errors.Add(@{ + Url = $url + Error = $errorMsg + Occurrences = $occurrences + }) + } + } + } + +# Print summary +Write-Host "`n" + ("=" * 60) -ForegroundColor Cyan +Write-Host "Link Verification Summary" -ForegroundColor Cyan +Write-Host ("=" * 60) -ForegroundColor Cyan +Write-Host "Total URLs checked: $($results.Total)" -ForegroundColor White +Write-Host "Passed: $($results.Passed)" -ForegroundColor Green +Write-Host "Failed: $($results.Failed)" -ForegroundColor $(if ($results.Failed -gt 0) { "Red" } else { "Green" }) +Write-Host "Skipped: $($results.Skipped)" -ForegroundColor Gray + +if ($results.Failed -gt 0) { + Write-Host "`nFailed Links:" -ForegroundColor Red + foreach ($failedLink in $results.Errors) { + Write-Host " • $($failedLink.Url)" -ForegroundColor Red + Write-Host " Error: $($failedLink.Error)" -ForegroundColor DarkGray + Write-Host " Occurrences: $($failedLink.Occurrences.Count)" -ForegroundColor DarkGray + } + + Write-Host "`n❌ Link verification failed!" -ForegroundColor Red + exit 1 +} +else { + Write-Host "`n✅ All links verified successfully!" -ForegroundColor Green +} + +# Write to GitHub Actions step summary if running in a workflow +if ($env:GITHUB_STEP_SUMMARY) { + $summaryContent = @" + +# Markdown Link Verification Results + +## Summary +- **Total URLs checked:** $($results.Total) +- **Passed:** ✅ $($results.Passed) +- **Failed:** $(if ($results.Failed -gt 0) { "❌" } else { "✅" }) $($results.Failed) +- **Skipped:** $($results.Skipped) + +"@ + + if ($results.Failed -gt 0) { + $summaryContent += @" + +## Failed Links + +| URL | Error | Occurrences | +|-----|-------|-------------| + +"@ + foreach ($failedLink in $results.Errors) { + $summaryContent += "| $($failedLink.Url) | $($failedLink.Error) | $($failedLink.Occurrences.Count) |`n" + } + + $summaryContent += @" + +
+Click to see all failed link locations + +"@ + foreach ($failedLink in $results.Errors) { + $summaryContent += "`n### $($failedLink.Url)`n" + $summaryContent += "**Error:** $($failedLink.Error)`n`n" + foreach ($occurrence in $failedLink.Occurrences) { + $summaryContent += "- `$($occurrence.Path):$($occurrence.Line):$($occurrence.Column)`n" + } + } + $summaryContent += "`n
`n" + } + else { + $summaryContent += "`n## ✅ All links verified successfully!`n" + } + + Write-Verbose -Verbose "Writing `n $summaryContent `n to ${env:GITHUB_STEP_SUMMARY}" + $summaryContent | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append + Write-Verbose -Verbose "Summary written to GitHub Actions step summary" +} + diff --git a/.github/actions/infrastructure/markdownlinks/action.yml b/.github/actions/infrastructure/markdownlinks/action.yml new file mode 100644 index 00000000000..de2952252d4 --- /dev/null +++ b/.github/actions/infrastructure/markdownlinks/action.yml @@ -0,0 +1,110 @@ +name: 'Verify Markdown Links' +description: 'Verify all links in markdown files using PowerShell and Markdig' +author: 'PowerShell Team' + +inputs: + timeout-sec: + description: 'Timeout in seconds for HTTP requests' + required: false + default: '30' + maximum-retry-count: + description: 'Maximum number of retries for failed requests' + required: false + default: '2' + +outputs: + total-links: + description: 'Total number of unique links checked' + value: ${{ steps.verify.outputs.total }} + passed-links: + description: 'Number of links that passed verification' + value: ${{ steps.verify.outputs.passed }} + failed-links: + description: 'Number of links that failed verification' + value: ${{ steps.verify.outputs.failed }} + skipped-links: + description: 'Number of links that were skipped' + value: ${{ steps.verify.outputs.skipped }} + +runs: + using: 'composite' + steps: + - name: Get changed markdown files + id: changed-files + uses: "./.github/actions/infrastructure/get-changed-files" + with: + filter: '*.md' + event-types: 'pull_request,push' + + - name: Verify markdown links + id: verify + shell: pwsh + env: + CHANGED_FILES_JSON: ${{ steps.changed-files.outputs.files }} + run: | + Write-Host "Starting markdown link verification..." -ForegroundColor Cyan + + # Get changed markdown files from environment variable (secure against injection) + $changedFilesJson = $env:CHANGED_FILES_JSON + $changedFiles = $changedFilesJson | ConvertFrom-Json + + if ($changedFiles.Count -eq 0) { + Write-Host "No markdown files changed, skipping verification" -ForegroundColor Yellow + "total=0" >> $env:GITHUB_OUTPUT + "passed=0" >> $env:GITHUB_OUTPUT + "failed=0" >> $env:GITHUB_OUTPUT + "skipped=0" >> $env:GITHUB_OUTPUT + exit 0 + } + + Write-Host "Changed markdown files: $($changedFiles.Count)" -ForegroundColor Cyan + $changedFiles | ForEach-Object { Write-Host " - $_" -ForegroundColor Gray } + + # Build parameters for each file + $params = @{ + File = $changedFiles + TimeoutSec = [int]'${{ inputs.timeout-sec }}' + MaximumRetryCount = [int]'${{ inputs.maximum-retry-count }}' + } + + # Run the verification script + $scriptPath = Join-Path '${{ github.action_path }}' 'Verify-MarkdownLinks.ps1' + + # Capture output and parse results + $output = & $scriptPath @params 2>&1 | Tee-Object -Variable capturedOutput + + # Try to extract metrics from output + $totalLinks = 0 + $passedLinks = 0 + $failedLinks = 0 + $skippedLinks = 0 + + foreach ($line in $capturedOutput) { + if ($line -match 'Total URLs checked: (\d+)') { + $totalLinks = $Matches[1] + } + elseif ($line -match 'Passed: (\d+)') { + $passedLinks = $Matches[1] + } + elseif ($line -match 'Failed: (\d+)') { + $failedLinks = $Matches[1] + } + elseif ($line -match 'Skipped: (\d+)') { + $skippedLinks = $Matches[1] + } + } + + # Set outputs + "total=$totalLinks" >> $env:GITHUB_OUTPUT + "passed=$passedLinks" >> $env:GITHUB_OUTPUT + "failed=$failedLinks" >> $env:GITHUB_OUTPUT + "skipped=$skippedLinks" >> $env:GITHUB_OUTPUT + + Write-Host "Action completed" -ForegroundColor Cyan + + # Exit with the same code as the verification script + exit $LASTEXITCODE + +branding: + icon: 'link' + color: 'blue' diff --git a/.github/actions/infrastructure/merge-conflict-checker/README.md b/.github/actions/infrastructure/merge-conflict-checker/README.md new file mode 100644 index 00000000000..b53d6f99964 --- /dev/null +++ b/.github/actions/infrastructure/merge-conflict-checker/README.md @@ -0,0 +1,86 @@ +# Merge Conflict Checker + +This composite GitHub Action checks for Git merge conflict markers in files changed in pull requests. + +## Purpose + +Automatically detects leftover merge conflict markers (`<<<<<<<`, `=======`, `>>>>>>>`) in pull request files to prevent them from being merged into the codebase. + +## Usage + +### In a Workflow + +```yaml +- name: Check for merge conflict markers + uses: "./.github/actions/infrastructure/merge-conflict-checker" +``` + +### Complete Example + +```yaml +jobs: + merge_conflict_check: + name: Check for Merge Conflict Markers + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + permissions: + pull-requests: read + contents: read + steps: + - name: checkout + uses: actions/checkout@v5 + + - name: Check for merge conflict markers + uses: "./.github/actions/infrastructure/merge-conflict-checker" +``` + +## How It Works + +1. **File Detection**: Uses GitHub's API to get the list of files changed in the pull request +2. **Marker Scanning**: Reads each changed file and searches for the following markers: + - `<<<<<<<` (conflict start marker) + - `=======` (conflict separator) + - `>>>>>>>` (conflict end marker) +3. **Result Reporting**: + - If markers are found, the action fails and lists all affected files + - If no markers are found, the action succeeds + +## Outputs + +- `files-checked`: Number of files that were checked +- `conflicts-found`: Number of files containing merge conflict markers + +## Behavior + +- **Event Support**: Only works with `pull_request` events +- **File Handling**: + - Checks only files that were added, modified, or renamed + - Skips deleted files + - **Filters out `*.cs` files** (C# files are excluded from merge conflict checking) + - Skips binary/unreadable files + - Skips directories +- **Empty File List**: Gracefully handles cases where no files need checking (e.g., PRs that only delete files) + +## Example Output + +When conflict markers are detected: + +``` +❌ Merge conflict markers detected in the following files: + - src/example.cs + Markers found: <<<<<<<, =======, >>>>>>> + - README.md + Markers found: <<<<<<<, =======, >>>>>>> + +Please resolve these conflicts before merging. +``` + +When no markers are found: + +``` +✅ No merge conflict markers found +``` + +## Integration + +This action is integrated into the `linux-ci.yml` workflow and runs automatically on all pull requests to ensure code quality before merging. diff --git a/.github/actions/infrastructure/merge-conflict-checker/action.yml b/.github/actions/infrastructure/merge-conflict-checker/action.yml new file mode 100644 index 00000000000..41c7d2ad941 --- /dev/null +++ b/.github/actions/infrastructure/merge-conflict-checker/action.yml @@ -0,0 +1,37 @@ +name: 'Check for Merge Conflict Markers' +description: 'Checks for Git merge conflict markers in changed files for pull requests' +author: 'PowerShell Team' + +outputs: + files-checked: + description: 'Number of files checked for merge conflict markers' + value: ${{ steps.check.outputs.files-checked }} + conflicts-found: + description: 'Number of files with merge conflict markers' + value: ${{ steps.check.outputs.conflicts-found }} + +runs: + using: 'composite' + steps: + - name: Get changed files + id: changed-files + uses: "./.github/actions/infrastructure/get-changed-files" + + - name: Check for merge conflict markers + id: check + shell: pwsh + env: + CHANGED_FILES_JSON: ${{ steps.changed-files.outputs.files }} + run: | + # Get changed files from environment variable (secure against injection) + $changedFilesJson = $env:CHANGED_FILES_JSON + # Ensure we always have an array (ConvertFrom-Json returns null for empty JSON arrays) + $changedFiles = @($changedFilesJson | ConvertFrom-Json) + + # Import ci.psm1 and run the check + Import-Module "$env:GITHUB_WORKSPACE/tools/ci.psm1" -Force + Test-MergeConflictMarker -File $changedFiles -WorkspacePath $env:GITHUB_WORKSPACE + +branding: + icon: 'alert-triangle' + color: 'red' diff --git a/.github/actions/infrastructure/path-filters/action.yml b/.github/actions/infrastructure/path-filters/action.yml index 78426bdff03..af23540256d 100644 --- a/.github/actions/infrastructure/path-filters/action.yml +++ b/.github/actions/infrastructure/path-filters/action.yml @@ -26,12 +26,22 @@ outputs: buildModuleChanged: description: 'Build module changes' value: ${{ steps.filter.outputs.buildModuleChanged }} + packagingChanged: + description: 'Packaging related changes' + value: ${{ steps.filter.outputs.packagingChanged }} runs: using: composite steps: + - name: Get changed files + id: get-files + if: github.event_name == 'pull_request' + uses: "./.github/actions/infrastructure/get-changed-files" + - name: Check if GitHubWorkflowChanges is present id: filter - uses: actions/github-script@v7.0.1 + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + env: + FILES_JSON: ${{ steps.get-files.outputs.files }} with: github-token: ${{ inputs.GITHUB_TOKEN }} script: | @@ -50,40 +60,71 @@ runs: return; } - console.log(`Getting files changed in PR #${context.issue.number}`); - - // Fetch the list of files changed in the PR - let files = []; - let page = 1; - let fetchedFiles; - do { - fetchedFiles = await github.rest.pulls.listFiles({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.issue.number, - per_page: 100, - page: page++ - }); - files = files.concat(fetchedFiles.data); - } while (fetchedFiles.data.length > 0); - - const actionsChanged = files.some(file => file.filename.startsWith('.github/actions')); - const workflowsChanged = files.some(file => file.filename.startsWith('.github/workflows')); + // Get files from environment variable (secure against injection) + const files = JSON.parse(process.env.FILES_JSON || '[]'); + + // Calculate hash for verification (matches get-changed-files action) + const crypto = require('crypto'); + const filesJson = JSON.stringify(files.sort()); + const hash = crypto.createHash('sha256').update(filesJson).digest('hex').substring(0, 8); + console.log(`Received ${files.length} files (hash: ${hash})`); + + // Analyze changes with detailed logging + core.startGroup('Path Filter Analysis'); + + const actionsChanged = files.some(file => file.startsWith('.github/actions')); + console.log(`✓ Actions changed: ${actionsChanged}`); + + const workflowsChanged = files.some(file => file.startsWith('.github/workflows')); + console.log(`✓ Workflows changed: ${workflowsChanged}`); + const githubChanged = actionsChanged || workflowsChanged; + console.log(`→ GitHub changed (actions OR workflows): ${githubChanged}`); + + const toolsCiPsm1Changed = files.some(file => file === 'tools/ci.psm1'); + console.log(`✓ tools/ci.psm1 changed: ${toolsCiPsm1Changed}`); + + const toolsBuildCommonChanged = files.some(file => file.startsWith('tools/buildCommon/')); + console.log(`✓ tools/buildCommon/ changed: ${toolsBuildCommonChanged}`); - const toolsCiPsm1Changed = files.some(file => file.filename.startsWith('tools/ci.psm1')); - const toolsBuildCommonChanged = files.some(file => file.filename.startsWith('tools/buildCommon/')); const toolsChanged = toolsCiPsm1Changed || toolsBuildCommonChanged; + console.log(`→ Tools changed: ${toolsChanged}`); + + const propsChanged = files.some(file => file.endsWith('.props')); + console.log(`✓ Props files changed: ${propsChanged}`); - const propsChanged = files.some(file => file.filename.endsWith('.props')); + const testsChanged = files.some(file => file.startsWith('test/powershell/') || file.startsWith('test/tools/') || file.startsWith('test/xUnit/')); + console.log(`✓ Tests changed: ${testsChanged}`); - const testsChanged = files.some(file => file.filename.startsWith('test/powershell/') || file.filename.startsWith('test/tools/') || file.filename.startsWith('test/xUnit/')); + const mainSourceChanged = files.some(file => file.startsWith('src/')); + console.log(`✓ Main source (src/) changed: ${mainSourceChanged}`); - const mainSourceChanged = files.some(file => file.filename.startsWith('src/')); + const buildModuleChanged = files.some(file => file === 'build.psm1'); + console.log(`✓ build.psm1 changed: ${buildModuleChanged}`); - const buildModuleChanged = files.some(file => file.filename.startsWith('build.psm1')); + const globalConfigChanged = files.some(file => file === '.globalconfig' || file === 'nuget.config' || file === 'global.json'); + console.log(`✓ Global config changed: ${globalConfigChanged}`); - const source = mainSourceChanged || toolsChanged || githubChanged || propsChanged || testsChanged; + const packagingChanged = files.some(file => + file === '.github/workflows/windows-ci.yml' || + file === '.github/workflows/linux-ci.yml' || + file.startsWith('assets/wix/') || + file === 'PowerShell.Common.props' || + file.match(/^src\/.*\.csproj$/) || + file.startsWith('test/packaging/windows/') || + file.startsWith('test/packaging/linux/') || + file.startsWith('tools/packaging/') || + file.startsWith('tools/wix/') + ) || + buildModuleChanged || + globalConfigChanged || + toolsCiPsm1Changed; + console.log(`→ Packaging changed: ${packagingChanged}`); + + const source = mainSourceChanged || toolsChanged || githubChanged || propsChanged || testsChanged || globalConfigChanged; + console.log(`→ Source (composite): ${source}`); + + core.endGroup(); core.setOutput('toolsChanged', toolsChanged); core.setOutput('githubChanged', githubChanged); @@ -91,15 +132,6 @@ runs: core.setOutput('testsChanged', testsChanged); core.setOutput('mainSourceChanged', mainSourceChanged); core.setOutput('buildModuleChanged', buildModuleChanged); + core.setOutput('globalConfigChanged', globalConfigChanged); + core.setOutput('packagingChanged', packagingChanged); core.setOutput('source', source); - - - name: Capture outputs - run: | - Write-Verbose -Verbose "source: ${{ steps.filter.outputs.source }}" - Write-Verbose -Verbose "github: ${{ steps.filter.outputs.githubChanged }}" - Write-Verbose -Verbose "tools: ${{ steps.filter.outputs.toolsChanged }}" - Write-Verbose -Verbose "props: ${{ steps.filter.outputs.propsChanged }}" - Write-Verbose -Verbose "tests: ${{ steps.filter.outputs.testsChanged }}" - Write-Verbose -Verbose "mainSource: ${{ steps.filter.outputs.mainSourceChanged }}" - Write-Verbose -Verbose "buildModule: ${{ steps.filter.outputs.buildModuleChanged }}" - shell: pwsh diff --git a/.github/actions/test/linux-packaging/action.yml b/.github/actions/test/linux-packaging/action.yml index b4a9c3b55c0..ce37a38c8b7 100644 --- a/.github/actions/test/linux-packaging/action.yml +++ b/.github/actions/test/linux-packaging/action.yml @@ -1,95 +1,69 @@ name: linux_packaging -description: 'Test very basic Linux packaging' - -# This isn't working yet -# It fails with - -# ERROR: While executing gem ... (Gem::FilePermissionError) -# You don't have write permissions for the /var/lib/gems/2.7.0 directory. -# WARNING: Installation of gem dotenv 2.8.1 failed! Must resolve manually. +description: 'Linux packaging for PowerShell' runs: using: composite steps: - name: Capture Environment if: success() || failure() - run: 'Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose' + run: |- + Import-Module ./tools/ci.psm1 + Show-Environment shell: pwsh - - name: Download Build Artifacts - uses: actions/download-artifact@v4 + + - uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 with: - path: "${{ github.workspace }}" - - name: Capture Artifacts Directory - continue-on-error: true - run: Get-ChildItem "${{ github.workspace }}/build/*" -Recurse - shell: pwsh + global-json-file: ./global.json - name: Bootstrap run: |- Import-Module ./build.psm1 Start-PSBootstrap -Scenario Package + Import-Module ./tools/ci.psm1 + Invoke-CIInstall -SkipUser shell: pwsh - - name: Capture Artifacts Directory - continue-on-error: true - run: Import-Module ./build.psm1 - shell: pwsh - - name: Extract Files - uses: actions/github-script@v7.0.0 - env: - DESTINATION_FOLDER: "${{ github.workspace }}/bins" - ARCHIVE_FILE_PATTERNS: "${{ github.workspace }}/build/build.zip" - with: - script: |- - const fs = require('fs').promises - const path = require('path') - const target = path.resolve(process.env.DESTINATION_FOLDER) - const patterns = process.env.ARCHIVE_FILE_PATTERNS - const globber = await glob.create(patterns) - await io.mkdirP(path.dirname(target)) - for await (const file of globber.globGenerator()) { - if ((await fs.lstat(file)).isDirectory()) continue - await exec.exec(`7z x ${file} -o${target} -aoa`) - } - - name: Fix permissions - continue-on-error: true + + - name: Build and Package run: |- - find "${{ github.workspace }}/bins" -type d -exec chmod +rwx {} \; - find "${{ github.workspace }}/bins" -type f -exec chmod +rw {} \; - shell: bash - - name: Capture Extracted Build ZIP - continue-on-error: true - run: Get-ChildItem "${{ github.workspace }}/bins/*" -Recurse -ErrorAction SilentlyContinue + Import-Module ./tools/ci.psm1 + $releaseTag = Get-ReleaseTag + Start-PSBuild -Configuration 'Release' -ReleaseTag $releaseTag + Invoke-CIFinish shell: pwsh - - name: Packaging Tests - if: success() + + - name: Install Pester run: |- Import-Module ./tools/ci.psm1 - Restore-PSOptions -PSOptionsPath '${{ github.workspace }}/build/psoptions.json' - $options = (Get-PSOptions) - $rootPath = '${{ github.workspace }}/bins' - $originalRootPath = Split-Path -path $options.Output - $path = Join-Path -path $rootPath -ChildPath (split-path -leaf -path $originalRootPath) - $pwshPath = Join-Path -path $path -ChildPath 'pwsh' - chmod a+x $pwshPath - $options.Output = $pwshPath - Set-PSOptions $options - Invoke-CIFinish + Install-CIPester shell: pwsh - - name: Upload packages + + - name: Validate Package Names run: |- - Get-ChildItem "${env:BUILD_ARTIFACTSTAGINGDIRECTORY}/*.deb" -Recurse | ForEach-Object { - $packagePath = $_.FullName - Write-Host "Uploading $packagePath" - Write-Host "##vso[artifact.upload containerfolder=deb;artifactname=deb]$packagePath" - } - Get-ChildItem "${env:BUILD_ARTIFACTSTAGINGDIRECTORY}/*.rpm" -Recurse | ForEach-Object { - $packagePath = $_.FullName - Write-Host "Uploading $packagePath" - Write-Host "##vso[artifact.upload containerfolder=rpm;artifactname=rpm]$packagePath" - } - Get-ChildItem "${env:BUILD_ARTIFACTSTAGINGDIRECTORY}/*.tar.gz" -Recurse | ForEach-Object { - $packagePath = $_.FullName - Write-Host "Uploading $packagePath" - Write-Host "##vso[artifact.upload containerfolder=rpm;artifactname=rpm]$packagePath" + # Run Pester tests to validate package names + Import-Module Pester -Force + $testResults = Invoke-Pester -Path ./test/packaging/linux/package-validation.tests.ps1 -PassThru + if ($testResults.FailedCount -gt 0) { + throw "Package validation tests failed" } shell: pwsh + + - name: Upload deb packages + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: packages-deb + path: ${{ runner.workspace }}/packages/*.deb + if-no-files-found: ignore + + - name: Upload rpm packages + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: packages-rpm + path: ${{ runner.workspace }}/packages/*.rpm + if-no-files-found: ignore + + - name: Upload tar.gz packages + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: packages-tar + path: ${{ runner.workspace }}/packages/*.tar.gz + if-no-files-found: ignore diff --git a/.github/actions/test/nix/action.yml b/.github/actions/test/nix/action.yml index b338c398340..ab30e0d9ce6 100644 --- a/.github/actions/test/nix/action.yml +++ b/.github/actions/test/nix/action.yml @@ -14,6 +14,9 @@ inputs: required: false default: ctrf type: string + GITHUB_TOKEN: + description: 'GitHub token for API authentication' + required: true runs: using: composite @@ -21,14 +24,12 @@ runs: - name: Capture Environment if: success() || failure() run: |- - Import-Module ./build.psm1 - Write-LogGroupStart -Title 'Environment' - Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose - Write-LogGroupEnd -Title 'Environment' + Import-Module ./tools/ci.psm1 + Show-Environment shell: pwsh - name: Download Build Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: path: "${{ github.workspace }}" @@ -41,10 +42,55 @@ runs: Write-LogGroupEnd -Title 'Artifacts Directory' shell: pwsh - - uses: actions/setup-dotnet@v4 + - uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1 with: global-json-file: ./global.json + - name: Set Package Name by Platform + id: set_package_name + shell: pwsh + run: |- + Import-Module ./.github/workflows/GHWorkflowHelper/GHWorkflowHelper.psm1 + $platform = $env:RUNNER_OS + Write-Host "Runner platform: $platform" + if ($platform -eq 'Linux') { + $packageName = 'DSC-*-x86_64-linux.tar.gz' + } elseif ($platform -eq 'macOS') { + $packageName = 'DSC-*-x86_64-apple-darwin.tar.gz' + } else { + throw "Unsupported platform: $platform" + } + + Set-GWVariable -Name "DSC_PACKAGE_NAME" -Value $packageName + + - name: Get Latest DSC Package Version + shell: pwsh + run: |- + Import-Module ./.github/workflows/GHWorkflowHelper/GHWorkflowHelper.psm1 + $headers = @{ + Authorization = "Bearer ${{ inputs.GITHUB_TOKEN }}" + } + $releases = Invoke-RestMethod -Uri "https://api.github.com/repos/PowerShell/Dsc/releases" -Headers $headers + $latestRelease = $releases | Where-Object { $v = $_.name.trim("v"); $semVer = [System.Management.Automation.SemanticVersion]::new($v); if ($semVer.Major -eq 3 -and $semVer.Minor -ge 2) { $_ } } | Select-Object -First 1 + $latestVersion = $latestRelease.tag_name.TrimStart("v") + Write-Host "Latest DSC Version: $latestVersion" + + $packageName = "$env:DSC_PACKAGE_NAME" + + Write-Host "Package Name: $packageName" + + $downloadUrl = $latestRelease.assets | Where-Object { $_.name -like "*$packageName*" } | Select-Object -First 1 | Select-Object -ExpandProperty browser_download_url + Write-Host "Download URL: $downloadUrl" + + $tempPath = Get-GWTempPath + + Invoke-RestMethod -Uri $downloadUrl -OutFile "$tempPath/DSC.tar.gz" -Verbose -Headers $headers + New-Item -ItemType Directory -Path "$tempPath/DSC" -Force -Verbose + tar xvf "$tempPath/DSC.tar.gz" -C "$tempPath/DSC" + $dscRoot = "$tempPath/DSC" + Write-Host "DSC Root: $dscRoot" + Set-GWVariable -Name "DSC_ROOT" -Value $dscRoot + - name: Bootstrap shell: pwsh run: |- @@ -55,7 +101,7 @@ runs: Write-LogGroupEnd -Title 'Bootstrap' - name: Extract Files - uses: actions/github-script@v7.0.0 + uses: actions/github-script@e69ef5462fd455e02edcaf4dd7708eda96b9eda0 # v7.0.0 env: DESTINATION_FOLDER: "${{ github.workspace }}/bins" ARCHIVE_FILE_PATTERNS: "${{ github.workspace }}/build/build.zip" diff --git a/.github/actions/test/process-pester-results/action.yml b/.github/actions/test/process-pester-results/action.yml index 27b94f6ebcb..44f2037626f 100644 --- a/.github/actions/test/process-pester-results/action.yml +++ b/.github/actions/test/process-pester-results/action.yml @@ -21,7 +21,7 @@ runs: - name: Upload testResults artifact if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: junit-pester-${{ inputs.name }} path: ${{ runner.workspace }}/testResults diff --git a/.github/actions/test/process-pester-results/process-pester-results.ps1 b/.github/actions/test/process-pester-results/process-pester-results.ps1 index 523de3bebaa..5804bec9a94 100644 --- a/.github/actions/test/process-pester-results/process-pester-results.ps1 +++ b/.github/actions/test/process-pester-results/process-pester-results.ps1 @@ -24,6 +24,7 @@ $testIgnoredCount = 0 $testSkippedCount = 0 $testInvalidCount = 0 +# Process test results and generate annotations for failures Get-ChildItem -Path "${TestResultsFolder}/*.xml" -Recurse | ForEach-Object { $results = [xml] (get-content $_.FullName) @@ -35,6 +36,61 @@ Get-ChildItem -Path "${TestResultsFolder}/*.xml" -Recurse | ForEach-Object { $testIgnoredCount += [int]$results.'test-results'.ignored $testSkippedCount += [int]$results.'test-results'.skipped $testInvalidCount += [int]$results.'test-results'.invalid + + # Generate GitHub Actions annotations for test failures + # Select failed test cases + if ("System.Xml.XmlDocumentXPathExtensions" -as [Type]) { + $failures = [System.Xml.XmlDocumentXPathExtensions]::SelectNodes($results.'test-results', './/test-case[@result = "Failure"]') + } + else { + $failures = $results.SelectNodes('.//test-case[@result = "Failure"]') + } + + foreach ($testfail in $failures) { + $description = $testfail.description + $testName = $testfail.name + $message = $testfail.failure.message + $stack_trace = $testfail.failure.'stack-trace' + + # Parse stack trace to get file and line info + $fileInfo = Get-PesterFailureFileInfo -StackTraceString $stack_trace + + if ($fileInfo.File) { + # Convert absolute path to relative path for GitHub Actions + $filePath = $fileInfo.File + + # GitHub Actions expects paths relative to the workspace root + if ($env:GITHUB_WORKSPACE) { + $workspacePath = $env:GITHUB_WORKSPACE + if ($filePath.StartsWith($workspacePath)) { + $filePath = $filePath.Substring($workspacePath.Length).TrimStart('/', '\') + # Normalize to forward slashes for consistency + $filePath = $filePath -replace '\\', '/' + } + } + + # Create annotation title + $annotationTitle = "Test Failure: $description / $testName" + + # Build the annotation message + $annotationMessage = $message -replace "`n", "%0A" -replace "`r" + + # Build and output the workflow command + $workflowCommand = "::error file=$filePath" + if ($fileInfo.Line) { + $workflowCommand += ",line=$($fileInfo.Line)" + } + $workflowCommand += ",title=$annotationTitle::$annotationMessage" + + Write-Host $workflowCommand + + # Output a link to the test run + if ($env:GITHUB_SERVER_URL -and $env:GITHUB_REPOSITORY -and $env:GITHUB_RUN_ID) { + $logUrl = "$($env:GITHUB_SERVER_URL)/$($env:GITHUB_REPOSITORY)/actions/runs/$($env:GITHUB_RUN_ID)" + Write-Host "Test logs: $logUrl" + } + } + } } @" diff --git a/.github/actions/test/verify_xunit/action.yml b/.github/actions/test/verify_xunit/action.yml deleted file mode 100644 index fccca27182f..00000000000 --- a/.github/actions/test/verify_xunit/action.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: verify_xunit -description: 'Verify xUnit Results' - -runs: - using: composite - steps: - - name: Download build artifacts - uses: actions/download-artifact@v4 - with: - path: "${{ github.workspace }}" - - name: Capture artifacts directory - continue-on-error: true - run: dir "${{ github.workspace }}\testResults-xunit\*" -Recurse - shell: pwsh - - name: Test - if: success() - run: |- - Import-Module .\tools\ci.psm1 - $xUnitTestResultsFile = "${{ github.workspace }}\testResults-xunit\xUnitTestResults.xml" - Test-XUnitTestResults -TestResultsFile $xUnitTestResultsFile - shell: pwsh diff --git a/.github/actions/test/windows/action.yml b/.github/actions/test/windows/action.yml index 734e30208f0..ddc5da4d664 100644 --- a/.github/actions/test/windows/action.yml +++ b/.github/actions/test/windows/action.yml @@ -14,6 +14,9 @@ inputs: required: false default: ctrf type: string + GITHUB_TOKEN: + description: 'GitHub token for API authentication' + required: true runs: using: composite @@ -21,14 +24,12 @@ runs: - name: Capture Environment if: success() || failure() run: |- - Import-Module ./build.psm1 - Write-LogGroupStart -Title 'Environment' - Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose - Write-LogGroupEnd -Title 'Environment' + Import-Module ./tools/ci.psm1 + Show-Environment shell: pwsh - name: Download Build Artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: path: "${{ github.workspace }}" @@ -41,10 +42,33 @@ runs: Write-LogGroupEnd -Title 'Artifacts Directory' shell: pwsh - - uses: actions/setup-dotnet@v4 + - uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1 with: global-json-file: .\global.json + - name: Get Latest DSC Package Version + shell: pwsh + run: |- + Import-Module .\.github\workflows\GHWorkflowHelper\GHWorkflowHelper.psm1 + $headers = @{ + Authorization = "Bearer ${{ inputs.GITHUB_TOKEN }}" + } + $releases = Invoke-RestMethod -Uri "https://api.github.com/repos/PowerShell/Dsc/releases" -Headers $headers + $latestRelease = $releases | Where-Object { $v = $_.name.trim("v"); $semVer = [System.Management.Automation.SemanticVersion]::new($v); if ($semVer.Major -eq 3 -and $semVer.Minor -ge 2) { $_ } } | Select-Object -First 1 + $latestVersion = $latestRelease.tag_name.TrimStart("v") + Write-Host "Latest DSC Version: $latestVersion" + + $downloadUrl = $latestRelease.assets | Where-Object { $_.name -like "DSC-*-x86_64-pc-windows-msvc.zip" } | Select-Object -First 1 | Select-Object -ExpandProperty browser_download_url + Write-Host "Download URL: $downloadUrl" + $tempPath = Get-GWTempPath + Invoke-RestMethod -Uri $downloadUrl -OutFile "$tempPath\DSC.zip" -Headers $headers + + $null = New-Item -ItemType Directory -Path "$tempPath\DSC" -Force + Expand-Archive -Path "$tempPath\DSC.zip" -DestinationPath "$tempPath\DSC" -Force + $dscRoot = "$tempPath\DSC" + Write-Host "DSC Root: $dscRoot" + Set-GWVariable -Name "DSC_ROOT" -Value $dscRoot + - name: Bootstrap shell: powershell run: |- diff --git a/.github/agents/SplitADOPipelines.agent.md b/.github/agents/SplitADOPipelines.agent.md new file mode 100644 index 00000000000..9454670061f --- /dev/null +++ b/.github/agents/SplitADOPipelines.agent.md @@ -0,0 +1,180 @@ +--- +name: SplitADOPipelines +description: This agent will implement and restructure the repository's existing ADO pipelines into Official and NonOfficial pipelines. +tools: ['vscode', 'execute', 'read', 'agent', 'edit', 'search', 'todo'] +--- + +This agent will implement and restructure the repository's existing ADO pipelines into Official and NonOfficial pipelines. + +A repository will have under the .pipelines directory a series of yaml files that define the ADO pipelines for the repository. + +First confirm if the pipelines are using a toggle switch for Official and NonOfficial. This will look something like this + +```yaml +parameters: + - name: templateFile + value: ${{ iif ( parameters.OfficialBuild, 'v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates', 'v2/OneBranch.NonOfficial.CrossPlat.yml@onebranchTemplates' ) }} +``` + +Followed by: + +```yaml +extends: + template: ${{ variables.templateFile }} +``` + +This is an indicator that this work needs to be done. This toggle switch is no longer allowed and the templates need to be hard coded. + +## Template Reference Convention (MUST follow) + +All `- template:` references to files **inside this repo** must use the **absolute** form anchored at the repo root, with the `@self` suffix: + +```yaml +- template: /.pipelines/templates//.yml@self +``` + +Do **not** use relative paths such as `templates/...`, `../templates/...`, or bare filenames. Rationale: + +- Absolute paths resolve identically regardless of where the referring file lives, so moving a pipeline file between directories (for example, into `.pipelines/NonOfficial/`) does not silently break includes. +- Relative paths are resolved by Azure DevOps against the directory of the referring file, which has caused real outages in this repo when a relative include was composed into a nonexistent nested path like `.pipelines/templates/stages/.pipelines/templates/...`. +- The majority of existing includes already use the absolute form; keeping new work consistent reduces review burden. + +The only acceptable non-absolute references are to external repositories resolved via the `resources.repositories` block, for example `v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates`. + +## Refactoring Steps + +### Step 1: Extract Shared Templates + +For each pipeline file that uses the toggle switch pattern (e.g., `PowerShell-Packages-Official.yml`): + +1. Create the `.pipelines/templates/variables` and `.pipelines/templates/stages` directories if they don't exist +2. Extract the **variables section** into `.pipelines/templates/variables/PowerShell-Packages-Variables.yml` +3. Extract the **stages section** into `.pipelines/templates/stages/PowerShell-Packages-Stages.yml` + +**IMPORTANT**: Only extract the `variables:` and `stages:` sections. All other sections (parameters, resources, extends, etc.) remain in the pipeline files. + +### Step 2: Create Official Pipeline (In-Place Refactoring) + +The original toggle-based file becomes the Official pipeline: + +1. **Keep the file in its original location** (e.g., `.pipelines/PowerShell-Packages-Official.yml` stays where it is) +2. Remove the toggle switch parameter (`templateFile` parameter) +3. Hard-code the Official template reference: + ```yaml + extends: + template: v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates + ``` +4. Replace the `variables:` section with a template reference: + ```yaml + variables: + - template: /.pipelines/templates/variables/PowerShell-Packages-Variables.yml@self + ``` +5. Replace the `stages:` section with a template reference: + ```yaml + stages: + - template: /.pipelines/templates/stages/PowerShell-Packages-Stages.yml@self + ``` + +### Step 3: Create NonOfficial Pipeline + +1. Create `.pipelines/NonOfficial` directory if it doesn't exist +2. Create the NonOfficial pipeline file (e.g., `.pipelines/NonOfficial/PowerShell-Packages-NonOfficial.yml`) +3. Copy the structure from the refactored Official pipeline +4. Hard-code the NonOfficial template reference: + ```yaml + extends: + template: v2/OneBranch.NonOfficial.CrossPlat.yml@onebranchTemplates + ``` +5. Reference the same shared templates: + ```yaml + variables: + - template: /.pipelines/templates/variables/PowerShell-Packages-Variables.yml@self + + stages: + - template: /.pipelines/templates/stages/PowerShell-Packages-Stages.yml@self + ``` + +**Note**: Always use **absolute** template paths of the form `/.pipelines/templates/...@self`. Do not use relative paths like `templates/...` or `../templates/...`. Absolute paths are anchored at the repo root and resolve consistently from any referring file, preventing breakage when files are moved between directories. + +### Step 4: Link NonOfficial Pipelines to NonOfficial Dependencies + +After creating NonOfficial pipelines, ensure they consume artifacts from other **NonOfficial** pipelines, not Official ones. + +1. **Check the `resources:` section** in each NonOfficial pipeline for `pipelines:` dependencies +2. **Identify Official pipeline references** that need to be changed to NonOfficial +3. **Update the `source:` field** to point to the NonOfficial version + +**Example Problem:** NonOfficial pipeline pointing to Official dependency +```yaml +resources: + pipelines: + - pipeline: CoOrdinatedBuildPipeline + source: 'PowerShell-Coordinated Binaries-Official' # ❌ Wrong - Official! +``` + +**Solution:** Update to NonOfficial dependency +```yaml +resources: + pipelines: + - pipeline: CoOrdinatedBuildPipeline + source: 'PowerShell-Coordinated Binaries-NonOfficial' # ✅ Correct - NonOfficial! +``` + +**IMPORTANT**: The `source:` field must match the **exact ADO pipeline definition name** as it appears in Azure DevOps, not necessarily the file name. + +### Step 5: Configure Release Environment Parameters (NonAzure Only) + +**This step only applies if the pipeline uses `category: NonAzure` in the release configuration.** + +If you detect this pattern in the original pipeline: + +```yaml +extends: + template: v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates # or NonOfficial + parameters: + release: + category: NonAzure +``` + +Then you must configure the `ob_release_environment` parameter when referencing the stages template. + +#### Official Pipeline Configuration + +In the Official pipeline (e.g., `.pipelines/PowerShell-Packages-Official.yml`): + +```yaml +stages: + - template: /.pipelines/templates/stages/PowerShell-Packages-Stages.yml@self + parameters: + ob_release_environment: Production +``` + +#### NonOfficial Pipeline Configuration + +In the NonOfficial pipeline (e.g., `.pipelines/NonOfficial/PowerShell-Packages-NonOfficial.yml`): + +```yaml +stages: + - template: /.pipelines/templates/stages/PowerShell-Packages-Stages.yml@self + parameters: + ob_release_environment: Test +``` + +#### Update Stages Template to Accept Parameter + +The extracted stages template (e.g., `.pipelines/templates/stages/PowerShell-Packages-Stages.yml`) must declare the parameter at the top: + +```yaml +parameters: + - name: ob_release_environment + type: string + +stages: + # ... rest of stages configuration using ${{ parameters.ob_release_environment }} +``` + +**IMPORTANT**: +- Only configure this for pipelines with `category: NonAzure` +- Official pipelines always use `ob_release_environment: Production` +- NonOfficial pipelines always use `ob_release_environment: Test` +- The stages template must accept this parameter and use it in the appropriate stage configurations diff --git a/.github/chatmodes/cherry-pick-commits.chatmode.md b/.github/chatmodes/cherry-pick-commits.chatmode.md new file mode 100644 index 00000000000..826ab11d56c --- /dev/null +++ b/.github/chatmodes/cherry-pick-commits.chatmode.md @@ -0,0 +1,78 @@ +# Cherry-Pick Commits Between Branches + +Cherry-pick recent commits from a source branch to a target branch without switching branches. + +## Instructions for Copilot + +1. **Confirm branches with the user** + - Ask the user to confirm the source and target branches + - If different branches are needed, update the configuration + +2. **Identify unique commits** + - Run: `git log .. --oneline --reverse` + - **IMPORTANT**: The commit count may be misleading if branches diverged from different base commits + - Compare the LAST few commits from each branch to identify actual missing commits: + - `git log --oneline -10` + - `git log --oneline -10` + - Look for commits with the same message but different SHAs (rebased commits) + - Show the user ONLY the truly missing commits (usually just the most recent ones) + +3. **Confirm with user before proceeding** + - If the commit count seems unusually high (e.g., 400+), STOP and verify semantically + - Ask: "I found X commits to cherry-pick. Shall I proceed?" + - If there are many commits, warn that this may take time + +4. **Execute the cherry-pick** + - Ensure the target branch is checked out first + - Run: `git cherry-pick ` for single commits + - Or: `git cherry-pick ` for multiple commits + - Apply commits in chronological order (oldest first) + +5. **Handle any issues** + - If conflicts occur, pause and ask user for guidance + - If empty commits occur, automatically skip with `git cherry-pick --skip` + +6. **Verify and report results** + - Run: `git log - --oneline` + - Show the user the newly applied commits + - Confirm the branch is now ahead by X commits + +## Key Git Commands + +```bash +# Find unique commits (may show full divergence if branches were rebased) +git log .. --oneline --reverse + +# Compare recent commits on each branch (more reliable for rebased branches) +git log --oneline -10 +git log --oneline -10 + +# Cherry-pick specific commits (when target is checked out) +git cherry-pick +git cherry-pick + +# Skip empty commits +git cherry-pick --skip + +# Verify result +git log - --oneline +``` + +## Common Scenarios + +- **Empty commits**: Automatically skip with `git cherry-pick --skip` +- **Conflicts**: Stop, show files with conflicts, ask user to resolve +- **Many commits**: Warn user and confirm before proceeding +- **Already applied**: These will result in empty commits that should be skipped +- **Diverged branches**: If branches diverged (rebased), `git log` may show the entire history difference + - The actual missing commits are usually only the most recent ones + - Compare commit messages from recent history on both branches + - Cherry-pick only commits that are semantically missing + +## Workflow Style + +Use an interactive, step-by-step approach: +- Show output from each command +- Ask for confirmation before major actions +- Provide clear status updates +- Handle errors gracefully with user guidance diff --git a/.github/dependabot.yml b/.github/dependabot.yml index fdb18f82c9d..45d2e8fe928 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,9 +8,25 @@ updates: labels: - "CL-BuildPackaging" - - package-ecosystem: docker + - package-ecosystem: "github-actions" + directory: "/" + target-branch: "release/*" + schedule: + interval: "daily" + labels: + - "CL-BuildPackaging" + + - package-ecosystem: "docker" directory: / schedule: interval: daily labels: - "CL-BuildPackaging" + + - package-ecosystem: "docker" + directory: "/" + target-branch: "release/*" + schedule: + interval: daily + labels: + - "CL-BuildPackaging" diff --git a/.github/instructions/build-and-packaging-steps.instructions.md b/.github/instructions/build-and-packaging-steps.instructions.md new file mode 100644 index 00000000000..934b1539593 --- /dev/null +++ b/.github/instructions/build-and-packaging-steps.instructions.md @@ -0,0 +1,127 @@ +--- +applyTo: + - ".github/actions/**/*.yml" + - ".github/workflows/**/*.yml" +--- + +# Build and Packaging Steps Pattern + +## Important Rule + +**Build and packaging must run in the same step OR you must save and restore PSOptions between steps.** + +## Why This Matters + +When `Start-PSBuild` runs, it creates PSOptions that contain build configuration details (runtime, configuration, output path, etc.). The packaging functions like `Start-PSPackage` and `Invoke-CIFinish` rely on these PSOptions to know where the build output is located and how it was built. + +GitHub Actions steps run in separate PowerShell sessions. This means PSOptions from one step are not available in the next step. + +## Pattern 1: Combined Build and Package (Recommended) + +Run build and packaging in the same step to keep PSOptions in memory: + +```yaml +- name: Build and Package + run: |- + Import-Module ./tools/ci.psm1 + $releaseTag = Get-ReleaseTag + Start-PSBuild -Configuration 'Release' -ReleaseTag $releaseTag + Invoke-CIFinish + shell: pwsh +``` + +**Benefits:** +- Simpler code +- No need for intermediate files +- PSOptions automatically available to packaging + +## Pattern 2: Separate Steps with Save/Restore + +If you must separate build and packaging into different steps: + +```yaml +- name: Build PowerShell + run: |- + Import-Module ./tools/ci.psm1 + $releaseTag = Get-ReleaseTag + Start-PSBuild -Configuration 'Release' -ReleaseTag $releaseTag + Save-PSOptions -PSOptionsPath "${{ runner.workspace }}/psoptions.json" + shell: pwsh + +- name: Create Packages + run: |- + Import-Module ./tools/ci.psm1 + Restore-PSOptions -PSOptionsPath "${{ runner.workspace }}/psoptions.json" + Invoke-CIFinish + shell: pwsh +``` + +**When to use:** +- When you need to run other steps between build and packaging +- When build and packaging require different permissions or environments + +## Common Mistakes + +### ❌ Incorrect: Separate steps without save/restore + +```yaml +- name: Build PowerShell + run: |- + Start-PSBuild -Configuration 'Release' + shell: pwsh + +- name: Create Packages + run: |- + Invoke-CIFinish # ❌ FAILS: PSOptions not available + shell: pwsh +``` + +### ❌ Incorrect: Using artifacts without PSOptions + +```yaml +- name: Download Build Artifacts + uses: actions/download-artifact@v4 + with: + name: build + +- name: Create Packages + run: |- + Invoke-CIFinish # ❌ FAILS: PSOptions not restored + shell: pwsh +``` + +## Related Functions + +- `Start-PSBuild` - Builds PowerShell and sets PSOptions +- `Save-PSOptions` - Saves PSOptions to a JSON file +- `Restore-PSOptions` - Loads PSOptions from a JSON file +- `Get-PSOptions` - Gets current PSOptions +- `Set-PSOptions` - Sets PSOptions +- `Start-PSPackage` - Creates packages (requires PSOptions) +- `Invoke-CIFinish` - Calls packaging (requires PSOptions on Linux/macOS) + +## Examples + +### Linux Packaging Action + +```yaml +- name: Build and Package + run: |- + Import-Module ./tools/ci.psm1 + $releaseTag = Get-ReleaseTag + Start-PSBuild -Configuration 'Release' -ReleaseTag $releaseTag + Invoke-CIFinish + shell: pwsh +``` + +### Windows Packaging Workflow + +```yaml +- name: Build and Package + run: | + Import-Module .\tools\ci.psm1 + Invoke-CIFinish -Runtime ${{ matrix.runtimePrefix }}-${{ matrix.architecture }} -channel ${{ matrix.channel }} + shell: pwsh +``` + +Note: `Invoke-CIFinish` for Windows includes both build and packaging in its logic when `Stage` contains 'Build'. diff --git a/.github/instructions/build-checkout-prerequisites.instructions.md b/.github/instructions/build-checkout-prerequisites.instructions.md new file mode 100644 index 00000000000..717aa6faa36 --- /dev/null +++ b/.github/instructions/build-checkout-prerequisites.instructions.md @@ -0,0 +1,148 @@ +--- +applyTo: + - ".github/**/*.yml" + - ".github/**/*.yaml" +--- + +# Build and Checkout Prerequisites for PowerShell CI + +This document describes the checkout and build prerequisites used in PowerShell's CI workflows. It is intended for GitHub Copilot sessions working with the build system. + +## Overview + +The PowerShell repository uses a standardized build process across Linux, Windows, and macOS CI workflows. Understanding the checkout configuration and the `Sync-PSTags` operation is crucial for working with the build system. + +## Checkout Configuration + +### Fetch Depth + +All CI workflows that build or test PowerShell use `fetch-depth: 1000` in the checkout step: + +```yaml +- name: checkout + uses: actions/checkout@v5 + with: + fetch-depth: 1000 +``` + +**Why 1000 commits?** +- The build system needs access to Git history to determine version information +- `Sync-PSTags` requires sufficient history to fetch and work with tags +- 1000 commits provides a reasonable balance between clone speed and having enough history for version calculation +- Shallow clones (fetch-depth: 1) would break versioning logic + +**Exceptions:** +- The `changes` job uses default fetch depth (no explicit `fetch-depth`) since it only needs to detect file changes +- The `analyze` job (CodeQL) uses `fetch-depth: '0'` (full history) for comprehensive security analysis +- Linux packaging uses `fetch-depth: 0` to ensure all tags are available for package version metadata + +### Workflows Using fetch-depth: 1000 + +- **Linux CI** (`.github/workflows/linux-ci.yml`): All build and test jobs +- **Windows CI** (`.github/workflows/windows-ci.yml`): All build and test jobs +- **macOS CI** (`.github/workflows/macos-ci.yml`): All build and test jobs + +## Sync-PSTags Operation + +### What is Sync-PSTags? + +`Sync-PSTags` is a PowerShell function defined in `build.psm1` that ensures Git tags from the upstream PowerShell repository are synchronized to the local clone. + +### Location + +- **Function Definition**: `build.psm1` (line 36-76) +- **Called From**: + - `.github/actions/build/ci/action.yml` (Bootstrap step, line 24) + - `tools/ci.psm1` (Invoke-CIInstall function, line 146) + +### How It Works + +```powershell +Sync-PSTags -AddRemoteIfMissing +``` + +The function: +1. Searches for a Git remote pointing to the official PowerShell repository: + - `https://github.com/PowerShell/PowerShell` + - `git@github.com:PowerShell/PowerShell` + +2. If no upstream remote exists and `-AddRemoteIfMissing` is specified: + - Adds a remote named `upstream` pointing to `https://github.com/PowerShell/PowerShell.git` + +3. Fetches all tags from the upstream remote: + ```bash + git fetch --tags --quiet upstream + ``` + +4. Sets `$script:tagsUpToDate = $true` to indicate tags are synchronized + +### Why Sync-PSTags is Required + +Tags are critical for: +- **Version Calculation**: `Get-PSVersion` uses `git describe --abbrev=0` to find the latest tag +- **Build Numbering**: CI builds use tag-based versioning for artifacts +- **Changelog Generation**: Release notes are generated based on tags +- **Package Metadata**: Package versions are derived from Git tags + +Without synchronized tags: +- Version detection would fail or return incorrect versions +- Builds might have inconsistent version numbers +- The build process would error when trying to determine the version + +### Bootstrap Step in CI Action + +The `.github/actions/build/ci/action.yml` includes this in the Bootstrap step: + +```yaml +- name: Bootstrap + if: success() + run: |- + Write-Verbose -Verbose "Running Bootstrap..." + Import-Module .\tools\ci.psm1 + Invoke-CIInstall -SkipUser + Write-Verbose -Verbose "Start Sync-PSTags" + Sync-PSTags -AddRemoteIfMissing + Write-Verbose -Verbose "End Sync-PSTags" + shell: pwsh +``` + +**Note**: `Sync-PSTags` is called twice: +1. Once by `Invoke-CIInstall` (in `tools/ci.psm1`) +2. Explicitly again in the Bootstrap step + +This redundancy ensures tags are available even if the first call encounters issues. + +## Best Practices for Copilot Sessions + +When working with the PowerShell CI system: + +1. **Always use `fetch-depth: 1000` or greater** when checking out code for build or test operations +2. **Understand that `Sync-PSTags` requires network access** to fetch tags from the upstream repository +3. **Don't modify the fetch-depth without understanding the impact** on version calculation +4. **If adding new CI workflows**, follow the existing pattern: + - Use `fetch-depth: 1000` for build/test jobs + - Call `Sync-PSTags -AddRemoteIfMissing` during bootstrap + - Ensure the upstream remote is properly configured + +5. **For local development**, developers should: + - Have the upstream remote configured + - Run `Sync-PSTags -AddRemoteIfMissing` before building + - Or use `Start-PSBuild` which handles this automatically + +## Related Files + +- `.github/actions/build/ci/action.yml` - Main CI build action +- `.github/workflows/linux-ci.yml` - Linux CI workflow +- `.github/workflows/windows-ci.yml` - Windows CI workflow +- `.github/workflows/macos-ci.yml` - macOS CI workflow +- `build.psm1` - Contains Sync-PSTags function definition +- `tools/ci.psm1` - CI-specific build functions that call Sync-PSTags + +## Summary + +The PowerShell CI system depends on: +1. **Adequate Git history** (fetch-depth: 1000) for version calculation +2. **Synchronized Git tags** via `Sync-PSTags` for accurate versioning +3. **Upstream remote access** to fetch official repository tags + +These prerequisites ensure consistent, accurate build versioning across all CI platforms. diff --git a/.github/instructions/build-configuration-guide.instructions.md b/.github/instructions/build-configuration-guide.instructions.md new file mode 100644 index 00000000000..d0384f4f307 --- /dev/null +++ b/.github/instructions/build-configuration-guide.instructions.md @@ -0,0 +1,150 @@ +--- +applyTo: + - "build.psm1" + - "tools/ci.psm1" + - ".github/**/*.yml" + - ".github/**/*.yaml" + - ".pipelines/**/*.yml" +--- + +# Build Configuration Guide + +## Choosing the Right Configuration + +### For Testing + +**Use: Default (Debug)** + +```yaml +- name: Build for Testing + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Start-PSBuild +``` + +**Why Debug:** +- Includes debugging symbols +- Better error messages +- Faster build times +- Suitable for xUnit and Pester tests + +**Do NOT use:** +- `-Configuration 'Release'` (unnecessary for tests) +- `-ReleaseTag` (not needed for tests) +- `-CI` (unless you specifically need Pester module) + +### For Release/Packaging + +**Use: Release with version tag and public NuGet feeds** + +```yaml +- name: Build for Release + shell: pwsh + run: | + Import-Module ./build.psm1 + Import-Module ./tools/ci.psm1 + Switch-PSNugetConfig -Source Public + $releaseTag = Get-ReleaseTag + Start-PSBuild -Configuration 'Release' -ReleaseTag $releaseTag +``` + +**Why Release:** +- Optimized binaries +- No debug symbols (smaller size) +- Production-ready + +**Why Switch-PSNugetConfig -Source Public:** +- Switches NuGet package sources to public feeds (nuget.org and public Azure DevOps feeds) +- Required for CI/CD environments that don't have access to private feeds +- Uses publicly available packages instead of Microsoft internal feeds + +### For Code Coverage + +**Use: CodeCoverage configuration** + +```yaml +- name: Build with Coverage + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Start-PSBuild -Configuration 'CodeCoverage' +``` + +## Platform Considerations + +### All Platforms + +Same commands work across Linux, Windows, and macOS: + +```yaml +strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] +runs-on: ${{ matrix.os }} +steps: + - name: Build PowerShell + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Start-PSBuild +``` + +### Output Locations + +**Linux/macOS:** +``` +src/powershell-unix/bin/Debug///publish/ +``` + +**Windows:** +``` +src/powershell-win-core/bin/Debug///publish/ +``` + +## Best Practices + +1. Use default configuration for testing +2. Avoid redundant parameters +3. Match configuration to purpose +4. Use `-CI` only when needed +5. Always specify `-ReleaseTag` for release or packaging builds +6. Use `Switch-PSNugetConfig -Source Public` in CI/CD for release builds + +## NuGet Feed Configuration + +### Switch-PSNugetConfig + +The `Switch-PSNugetConfig` function in `build.psm1` manages NuGet package source configuration. + +**Available Sources:** + +- **Public**: Uses public feeds (nuget.org and public Azure DevOps feeds) + - Required for: CI/CD environments, public builds, packaging + - Does not require authentication + +- **Private**: Uses internal PowerShell team feeds + - Required for: Internal development with preview packages + - Requires authentication credentials + +- **NuGetOnly**: Uses only nuget.org + - Required for: Minimal dependency scenarios + +**Usage:** + +```powershell +# Switch to public feeds (most common for CI/CD) +Switch-PSNugetConfig -Source Public + +# Switch to private feeds with authentication +Switch-PSNugetConfig -Source Private -UserName $userName -ClearTextPAT $pat + +# Switch to nuget.org only +Switch-PSNugetConfig -Source NuGetOnly +``` + +**When to Use:** + +- **Always use `-Source Public`** before building in CI/CD workflows +- Use before any build that will create packages for distribution +- Use in forks or environments without access to Microsoft internal feeds diff --git a/.github/instructions/code-review-branch-strategy.instructions.md b/.github/instructions/code-review-branch-strategy.instructions.md new file mode 100644 index 00000000000..191a677b912 --- /dev/null +++ b/.github/instructions/code-review-branch-strategy.instructions.md @@ -0,0 +1,230 @@ +--- +applyTo: "**/*" +--- + +# Code Review Branch Strategy Guide + +This guide helps GitHub Copilot provide appropriate feedback when reviewing code changes, particularly distinguishing between issues that should be fixed in the current branch versus the default branch. + +## Purpose + +When reviewing pull requests, especially those targeting release branches, it's important to identify whether an issue should be fixed in: +- **The current PR/branch** - Release-specific fixes or backports +- **The default branch first** - General bugs that exist in the main codebase + +## Branch Types and Fix Strategy + +### Release Branches (e.g., `release/v7.5`, `release/v7.4`) + +**Purpose:** Contain release-specific changes and critical backports + +**Should contain:** +- Release-specific configuration changes +- Critical bug fixes that are backported from the default branch +- Release packaging/versioning adjustments + +**Should NOT contain:** +- New general bug fixes that haven't been fixed in the default branch +- Refactoring or improvements that apply to the main codebase +- Workarounds for issues that exist in the default branch + +### Default/Main Branch (e.g., `master`, `main`) + +**Purpose:** Primary development branch for all ongoing work + +**Should contain:** +- All general bug fixes +- New features and improvements +- Refactoring and code quality improvements +- Fixes that will later be backported to release branches + +## Identifying Issues That Belong in the Default Branch + +When reviewing a PR targeting a release branch, look for these indicators that suggest the fix should be in the default branch first: + +### 1. The Root Cause Exists in Default Branch + +If the underlying issue exists in the default branch's code, it should be fixed there first. + +**Example:** +```yaml +# PR changes this in release/v7.5: +- $metadata = Get-Content "$repoRoot/tools/metadata.json" -Raw | ConvertFrom-Json ++ $metadata = Get-Content "$(Build.SourcesDirectory)/PowerShell/tools/metadata.json" -Raw | ConvertFrom-Json +``` + +**Analysis:** If `$repoRoot` is undefined because the template doesn't include its dependencies in BOTH the release branch AND the default branch, the fix should address the root cause in the default branch first. + +### 2. The Fix is a Workaround Rather Than a Proper Solution + +If the change introduces a workaround (hardcoded paths, special cases) rather than fixing the underlying design issue, it likely belongs in the default branch as a proper fix. + +**Example:** +- Using hardcoded paths instead of fixing variable initialization +- Adding special cases instead of fixing the logic +- Duplicating code instead of fixing shared dependencies + +### 3. The Issue Affects General Functionality + +If the issue affects general functionality not specific to a release, it should be fixed in the default branch. + +**Example:** +- Template dependencies that affect all pipelines +- Shared utility functions +- Common configuration issues + +## Providing Code Review Feedback + +### For Issues in the Current Branch + +When an issue is specific to the current branch or is a legitimate fix for the branch being targeted, **use the default code review feedback format** without any special branch-strategy commentary. + +### For Issues That Belong in the Default Branch + +1. **Provide the code review feedback** +2. **Explain why it should be fixed in the default branch** +3. **Provide an issue template** in markdown format + +**Example:** + +```markdown +The `channelSelection.yml` template relies on `$repoRoot` being set by `SetVersionVariables.yml`, but doesn't declare this dependency. This issue exists in both the release branch and the default branch. + +**This should be fixed in the default branch first**, then backported if needed. The proper fix is to ensure template dependencies are correctly declared, rather than using hardcoded paths as a workaround. + +--- + +**Suggested Issue for Default Branch:** + +### Issue Title +`channelSelection.yml` template missing dependency on `SetVersionVariables.yml` + +### Description +The `channelSelection.yml` template uses the `$repoRoot` variable but doesn't ensure it's set beforehand by including `SetVersionVariables.yml`. + +**Current State:** +- `channelSelection.yml` expects `$repoRoot` to be available +- Not all pipelines that use `channelSelection.yml` include `SetVersionVariables.yml` first +- This creates an implicit dependency that's not enforced + +**Expected State:** +Either: +1. `channelSelection.yml` should include `SetVersionVariables.yml` as a dependency, OR +2. `channelSelection.yml` should be refactored to not depend on `$repoRoot`, OR +3. Pipelines using `channelSelection.yml` should explicitly include `SetVersionVariables.yml` first + +**Files Affected:** +- `.pipelines/templates/channelSelection.yml` +- `.pipelines/templates/package-create-msix.yml` +- `.pipelines/templates/release-SetTagAndChangelog.yml` + +**Priority:** Medium +**Labels:** `Issue-Bug`, `Area-Build`, `Area-Pipeline` +``` + +## Issue Template Format + +When creating an issue template for the default branch, use this structure: + +```markdown +### Issue Title +[Clear, concise description of the problem] + +### Description +[Detailed explanation of the issue] + +**Current State:** +- [What's happening now] +- [Why it's problematic] + +**Expected State:** +- [What should happen] +- [Proposed solution(s)] + +**Files Affected:** +- [List of files] + +**Priority:** [Low/Medium/High/Critical] +**Labels:** [Suggested labels like `Issue-Bug`, `Area-*`] + +**Additional Context:** +[Any additional information, links to related issues, etc.] +``` + +## Common Scenarios + +### Scenario 1: Template Dependency Issues + +**Indicators:** +- Missing template includes +- Undefined variables from other templates +- Assumptions about pipeline execution order + +**Action:** Suggest fixing template dependencies in the default branch. + +### Scenario 2: Hardcoded Values + +**Indicators:** +- Hardcoded paths replacing variables +- Environment-specific values in shared code +- Magic strings or numbers + +**Action:** Suggest proper variable/parameter usage in the default branch. + +### Scenario 3: Logic Errors + +**Indicators:** +- Incorrect conditional logic +- Missing error handling +- Race conditions + +**Action:** Suggest fixing the logic in the default branch unless it's release-specific. + +### Scenario 4: Legitimate Release Branch Fixes + +**Indicators:** +- Version-specific configuration +- Release packaging changes +- Backport of already-fixed default branch issue + +**Action:** Provide normal code review feedback for the current PR. + +## Best Practices + +1. **Always check if the issue exists in the default branch** before suggesting a release-branch-only fix +2. **Prefer fixing root causes over workarounds** +3. **Provide clear rationale** for why a fix belongs in the default branch +4. **Include actionable issue templates** so users can easily create issues +5. **Be helpful, not blocking** - provide the feedback even if you can't enforce where it's fixed + +## Examples of Good vs. Bad Approaches + +### ❌ Bad: Workaround in Release Branch Only + +```yaml +# In release/v7.5 only +- pwsh: | + $metadata = Get-Content "$(Build.SourcesDirectory)/PowerShell/tools/metadata.json" -Raw +``` + +**Why bad:** Hardcodes path to work around missing `$repoRoot`, doesn't fix the default branch. + +### ✅ Good: Fix in Default Branch, Then Backport + +```yaml +# In default branch first +- template: SetVersionVariables.yml@self # Ensures $repoRoot is set +- template: channelSelection.yml@self # Now can use $repoRoot +``` + +**Why good:** Fixes the root cause by ensuring dependencies are declared, then backport to release if needed. + +## When in Doubt + +If you're unsure whether an issue should be fixed in the current branch or the default branch, ask yourself: + +1. Does this issue exist in the default branch? +2. Is this a workaround or a proper fix? +3. Will other branches/releases benefit from this fix? + +If the answer to any of these is "yes," suggest fixing it in the default branch first. diff --git a/.github/instructions/instruction-file-format.instructions.md b/.github/instructions/instruction-file-format.instructions.md new file mode 100644 index 00000000000..7c4e0bdd13d --- /dev/null +++ b/.github/instructions/instruction-file-format.instructions.md @@ -0,0 +1,220 @@ +--- +applyTo: + - ".github/instructions/**/*.instructions.md" +--- + +# Instruction File Format Guide + +This document describes the format and guidelines for creating custom instruction files for GitHub Copilot in the PowerShell repository. + +## File Naming Convention + +All instruction files must use the `.instructions.md` suffix: +- ✅ Correct: `build-checkout-prerequisites.instructions.md` +- ✅ Correct: `start-psbuild-basics.instructions.md` +- ❌ Incorrect: `build-guide.md` +- ❌ Incorrect: `instructions.md` + +## Required Frontmatter + +Every instruction file must start with YAML frontmatter containing an `applyTo` section: + +```yaml +--- +applyTo: + - "path/to/files/**/*.ext" + - "specific-file.ext" +--- +``` + +### applyTo Patterns + +Specify which files or directories these instructions apply to: + +**For workflow files:** +```yaml +applyTo: + - ".github/**/*.yml" + - ".github/**/*.yaml" +``` + +**For build scripts:** +```yaml +applyTo: + - "build.psm1" + - "tools/ci.psm1" +``` + +**For multiple contexts:** +```yaml +applyTo: + - "build.psm1" + - "tools/**/*.psm1" + - ".github/**/*.yml" +``` + +## Content Structure + +### 1. Clear Title + +Use a descriptive H1 heading after the frontmatter: + +```markdown +# Build Configuration Guide +``` + +### 2. Purpose or Overview + +Start with a brief explanation of what the instructions cover: + +```markdown +## Purpose + +This guide explains how to configure PowerShell builds for different scenarios. +``` + +### 3. Actionable Content + +Provide clear, actionable guidance: + +**✅ Good - Specific and actionable:** +```markdown +## Default Usage + +Use `Start-PSBuild` with no parameters for testing: + +```powershell +Import-Module ./tools/ci.psm1 +Start-PSBuild +``` +``` + +**❌ Bad - Vague and unclear:** +```markdown +## Usage + +You can use Start-PSBuild to build stuff. +``` + +### 4. Code Examples + +Include working code examples with proper syntax highlighting: + +```markdown +```yaml +- name: Build PowerShell + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Start-PSBuild +``` +``` + +### 5. Context and Rationale + +Explain why things are done a certain way: + +```markdown +**Why fetch-depth: 1000?** +- The build system needs Git history for version calculation +- Shallow clones would break versioning logic +``` + +## Best Practices + +### Be Concise + +- Focus on essential information +- Remove redundant explanations +- Use bullet points for lists + +### Be Specific + +- Provide exact commands and parameters +- Include file paths and line numbers when relevant +- Show concrete examples, not abstract concepts + +### Avoid Duplication + +- Don't repeat information from other instruction files +- Reference other files when appropriate +- Keep each file focused on one topic + +### Use Proper Formatting + +**Headers:** +- Use H1 (`#`) for the main title +- Use H2 (`##`) for major sections +- Use H3 (`###`) for subsections + +**Code blocks:** +- Always specify the language: ` ```yaml `, ` ```powershell `, ` ```bash ` +- Keep examples short and focused +- Test examples before including them + +**Lists:** +- Use `-` for unordered lists +- Use `1.` for ordered lists +- Keep list items concise + +## Example Structure + +```markdown +--- +applyTo: + - "relevant/files/**/*.ext" +--- + +# Title of Instructions + +Brief description of what these instructions cover. + +## Section 1 + +Content with examples. + +```language +code example +``` + +## Section 2 + +More specific guidance. + +### Subsection + +Detailed information when needed. + +## Best Practices + +- Actionable tip 1 +- Actionable tip 2 +``` + +## Maintaining Instructions + +### When to Create a New File + +Create a new instruction file when: +- Covering a distinct topic not addressed elsewhere +- The content is substantial enough to warrant its own file +- The `applyTo` scope is different from existing files + +### When to Update an Existing File + +Update an existing file when: +- Information is outdated +- New best practices emerge +- Examples need correction + +### When to Merge or Delete + +Merge or delete files when: +- Content is duplicated across multiple files +- A file is too small to be useful standalone +- Information is no longer relevant + +## Reference + +For more details, see: +- [GitHub Copilot Custom Instructions Documentation](https://docs.github.com/en/copilot/how-tos/configure-custom-instructions/add-repository-instructions) diff --git a/.github/instructions/log-grouping-guidelines.instructions.md b/.github/instructions/log-grouping-guidelines.instructions.md new file mode 100644 index 00000000000..ff845db4e4b --- /dev/null +++ b/.github/instructions/log-grouping-guidelines.instructions.md @@ -0,0 +1,181 @@ +--- +applyTo: + - "build.psm1" + - "tools/ci.psm1" + - ".github/**/*.yml" + - ".github/**/*.yaml" +--- + +# Log Grouping Guidelines for GitHub Actions + +## Purpose + +Guidelines for using `Write-LogGroupStart` and `Write-LogGroupEnd` to create collapsible log sections in GitHub Actions CI/CD runs. + +## Key Principles + +### 1. Groups Cannot Be Nested + +GitHub Actions does not support nested groups. Only use one level of grouping. + +**❌ Don't:** +```powershell +Write-LogGroupStart -Title "Outer Group" +Write-LogGroupStart -Title "Inner Group" +# ... operations ... +Write-LogGroupEnd -Title "Inner Group" +Write-LogGroupEnd -Title "Outer Group" +``` + +**✅ Do:** +```powershell +Write-LogGroupStart -Title "Operation A" +# ... operations ... +Write-LogGroupEnd -Title "Operation A" + +Write-LogGroupStart -Title "Operation B" +# ... operations ... +Write-LogGroupEnd -Title "Operation B" +``` + +### 2. Groups Should Be Substantial + +Only create groups for operations that generate substantial output (5+ lines). Small groups add clutter without benefit. + +**❌ Don't:** +```powershell +Write-LogGroupStart -Title "Generate Resource Files" +Write-Log -message "Run ResGen" +Start-ResGen +Write-LogGroupEnd -Title "Generate Resource Files" +``` + +**✅ Do:** +```powershell +Write-Log -message "Run ResGen (generating C# bindings for resx files)" +Start-ResGen +``` + +### 3. Groups Should Represent Independent Operations + +Each group should be a logically independent operation that users might want to expand/collapse separately. + +**✅ Good examples:** +- Install Native Dependencies +- Install .NET SDK +- Build PowerShell +- Restore NuGet Packages + +**❌ Bad examples:** +- Individual project restores (too granular) +- Small code generation steps (too small) +- Sub-steps of a larger operation (would require nesting) + +### 4. One Group Per Iteration Is Excessive + +Avoid putting log groups inside loops where each iteration creates a separate group. This would probably cause nesting. + +**❌ Don't:** +```powershell +$projects | ForEach-Object { + Write-LogGroupStart -Title "Restore Project: $_" + dotnet restore $_ + Write-LogGroupEnd -Title "Restore Project: $_" +} +``` + +**✅ Do:** +```powershell +Write-LogGroupStart -Title "Restore All Projects" +$projects | ForEach-Object { + Write-Log -message "Restoring $_" + dotnet restore $_ +} +Write-LogGroupEnd -Title "Restore All Projects" +``` + +## Usage Pattern + +```powershell +Write-LogGroupStart -Title "Descriptive Operation Name" +try { + # ... operation code ... + Write-Log -message "Status updates" +} +finally { + # Ensure group is always closed +} +Write-LogGroupEnd -Title "Descriptive Operation Name" +``` + +## When to Use Log Groups + +Use log groups for: +- Major build phases (bootstrap, restore, build, test, package) +- Installation operations (dependencies, SDKs, tools) +- Operations that produce 5+ lines of output +- Operations where users might want to collapse verbose output + +Don't use log groups for: +- Single-line operations +- Code that's already inside another group +- Loop iterations with minimal output per iteration +- Diagnostic or debug output that should always be visible + +## Examples from build.psm1 + +### Good Usage + +```powershell +function Start-PSBootstrap { + # Multiple independent operations, each with substantial output + Write-LogGroupStart -Title "Install Native Dependencies" + # ... apt-get/yum/brew install commands ... + Write-LogGroupEnd -Title "Install Native Dependencies" + + Write-LogGroupStart -Title "Install .NET SDK" + # ... dotnet installation ... + Write-LogGroupEnd -Title "Install .NET SDK" +} +``` + +### Avoid + +```powershell +# Too small - just 2-3 lines +Write-LogGroupStart -Title "Generate Resource Files (ResGen)" +Write-Log -message "Run ResGen" +Start-ResGen +Write-LogGroupEnd -Title "Generate Resource Files (ResGen)" +``` + +## GitHub Actions Syntax + +These functions emit GitHub Actions workflow commands: +- `Write-LogGroupStart` → `::group::Title` +- `Write-LogGroupEnd` → `::endgroup::` + +In the GitHub Actions UI, this renders as collapsible sections with the specified title. + +## Testing + +Test log grouping locally: +```powershell +$env:GITHUB_ACTIONS = 'true' +Import-Module ./build.psm1 +Write-LogGroupStart -Title "Test" +Write-Log -Message "Content" +Write-LogGroupEnd -Title "Test" +``` + +Output should show: +``` +::group::Test +Content +::endgroup:: +``` + +## References + +- [GitHub Actions: Grouping log lines](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#grouping-log-lines) +- `build.psm1`: `Write-LogGroupStart` and `Write-LogGroupEnd` function definitions diff --git a/.github/instructions/onebranch-condition-syntax.instructions.md b/.github/instructions/onebranch-condition-syntax.instructions.md new file mode 100644 index 00000000000..19bf331d9c3 --- /dev/null +++ b/.github/instructions/onebranch-condition-syntax.instructions.md @@ -0,0 +1,223 @@ +--- +applyTo: ".pipelines/**/*.{yml,yaml}" +--- + +# OneBranch Pipeline Condition Syntax + +## Overview +Azure Pipelines (OneBranch) uses specific syntax for referencing variables and parameters in condition expressions. Using the wrong syntax will cause conditions to fail silently or behave unexpectedly. + +## Variable Reference Patterns + +### In Condition Expressions + +**✅ Correct Pattern:** +```yaml +condition: eq(variables['VariableName'], 'value') +condition: or(eq(variables['VAR1'], 'true'), eq(variables['VAR2'], 'true')) +condition: and(succeeded(), eq(variables['Architecture'], 'fxdependent')) +``` + +**❌ Incorrect Patterns:** +```yaml +# Don't use $(VAR) string expansion in conditions +condition: eq('$(VariableName)', 'value') + +# Don't use direct variable references +condition: eq($VariableName, 'value') +``` + +### In Script Content (pwsh, bash, etc.) + +**✅ Correct Pattern:** +```yaml +- pwsh: | + $value = '$(VariableName)' + Write-Host "Value: $(VariableName)" +``` + +### In Input Fields + +**✅ Correct Pattern:** +```yaml +inputs: + serviceEndpoint: '$(ServiceEndpoint)' + sbConfigPath: '$(SBConfigPath)' +``` + +## Parameter References + +### Template Parameters (Compile-Time) + +**✅ Correct Pattern:** +```yaml +parameters: + - name: OfficialBuild + type: boolean + default: false + +steps: + - task: SomeTask@1 + condition: eq('${{ parameters.OfficialBuild }}', 'true') +``` + +Note: Parameters use `${{ parameters.Name }}` because they're evaluated at template compile-time. + +### Runtime Variables (Execution-Time) + +**✅ Correct Pattern:** +```yaml +steps: + - pwsh: | + Write-Host "##vso[task.setvariable variable=MyVar]somevalue" + displayName: Set Variable + + - task: SomeTask@1 + condition: eq(variables['MyVar'], 'somevalue') +``` + +## Common Scenarios + +### Scenario 1: Check if Variable Equals Value + +```yaml +- task: DoSomething@1 + condition: eq(variables['PREVIEW'], 'true') +``` + +### Scenario 2: Multiple Variable Conditions (OR) + +```yaml +- task: DoSomething@1 + condition: or(eq(variables['STABLE'], 'true'), eq(variables['LTS'], 'true')) +``` + +### Scenario 3: Multiple Variable Conditions (AND) + +```yaml +- task: DoSomething@1 + condition: and(succeeded(), eq(variables['Architecture'], 'fxdependent')) +``` + +### Scenario 4: Complex Conditions + +```yaml +- task: DoSomething@1 + condition: and( + succeededOrFailed(), + ne(variables['UseAzDevOpsFeed'], ''), + eq(variables['Build.SourceBranch'], 'refs/heads/master') + ) +``` + +### Scenario 5: Built-in Variables + +```yaml +- task: CodeQL3000Init@0 + condition: eq(variables['Build.SourceBranch'], 'refs/heads/master') + +- step: finalize + condition: eq(variables['Agent.JobStatus'], 'SucceededWithIssues') +``` + +### Scenario 6: Parameter vs Variable + +```yaml +parameters: + - name: OfficialBuild + type: boolean + +steps: + # Parameter condition (compile-time) + - task: SignFiles@1 + condition: eq('${{ parameters.OfficialBuild }}', 'true') + + # Variable condition (runtime) + - task: PublishArtifact@1 + condition: eq(variables['PUBLISH_ENABLED'], 'true') +``` + +## Why This Matters + +**String Expansion `$(VAR)` in Conditions:** +- When you use `'$(VAR)'` in a condition, Azure Pipelines attempts to expand it as a string +- If the variable is undefined or empty, it becomes an empty string `''` +- The condition `eq('', 'true')` will always be false +- This makes debugging difficult because there's no error message + +**Variables Array Syntax `variables['VAR']`:** +- This is the proper way to reference runtime variables in conditions +- Azure Pipelines correctly evaluates the variable's value +- Undefined variables are handled properly by the condition evaluator +- This is the standard pattern used throughout Azure Pipelines + +## Reference Examples + +Working examples can be found in: +- `.pipelines/templates/linux.yml` - Build.SourceBranch conditions +- `.pipelines/templates/windows-hosted-build.yml` - Architecture conditions +- `.pipelines/templates/compliance/apiscan.yml` - CODEQL_ENABLED conditions +- `.pipelines/templates/insert-nuget-config-azfeed.yml` - Complex AND/OR conditions + +## Quick Reference Table + +| Context | Syntax | Example | +|---------|--------|---------| +| Condition expression | `variables['Name']` | `condition: eq(variables['PREVIEW'], 'true')` | +| Script content | `$(Name)` | `pwsh: Write-Host "$(PREVIEW)"` | +| Task input | `$(Name)` | `inputs: path: '$(Build.SourcesDirectory)'` | +| Template parameter | `${{ parameters.Name }}` | `condition: eq('${{ parameters.Official }}', 'true')` | + +## Troubleshooting + +### Condition Always False +If your condition is always evaluating to false: +1. Check if you're using `'$(VAR)'` instead of `variables['VAR']` +2. Verify the variable is actually set (add a debug step to print the variable) +3. Check the variable value is exactly what you expect (case-sensitive) + +### Variable Not Found +If you get errors about variables not being found: +1. Ensure the variable is set before the condition is evaluated +2. Check that the variable name is spelled correctly +3. Verify the variable is in scope (job vs. stage vs. pipeline level) + +## Best Practices + +1. **Always use `variables['Name']` in conditions** - This is the correct Azure Pipelines pattern +2. **Use `$(Name)` for string expansion** in scripts and inputs +3. **Use `${{ parameters.Name }}` for template parameters** (compile-time) +4. **Add debug steps** to verify variable values when troubleshooting conditions +5. **Follow existing patterns** in the repository - grep for `condition:` to see examples + +## Common Mistakes + +❌ **Mistake 1: String expansion in condition** +```yaml +condition: eq('$(PREVIEW)', 'true') # WRONG +``` + +✅ **Fix:** +```yaml +condition: eq(variables['PREVIEW'], 'true') # CORRECT +``` + +❌ **Mistake 2: Missing quotes around parameter** +```yaml +condition: eq(${{ parameters.Official }}, true) # WRONG +``` + +✅ **Fix:** +```yaml +condition: eq('${{ parameters.Official }}', 'true') # CORRECT +``` + +❌ **Mistake 3: Mixing syntax** +```yaml +condition: or(eq('$(STABLE)', 'true'), eq(variables['LTS'], 'true')) # INCONSISTENT +``` + +✅ **Fix:** +```yaml +condition: or(eq(variables['STABLE'], 'true'), eq(variables['LTS'], 'true')) # CORRECT +``` diff --git a/.github/instructions/onebranch-restore-phase-pattern.instructions.md b/.github/instructions/onebranch-restore-phase-pattern.instructions.md new file mode 100644 index 00000000000..0945bb47c0b --- /dev/null +++ b/.github/instructions/onebranch-restore-phase-pattern.instructions.md @@ -0,0 +1,83 @@ +--- +applyTo: ".pipelines/**/*.{yml,yaml}" +--- + +# OneBranch Restore Phase Pattern + +## Overview +When steps need to run in the OneBranch restore phase (before the main build phase), the `ob_restore_phase` environment variable must be set in the `env:` block of **each individual step**. + +## Pattern + +### ✅ Correct (Working Pattern) +```yaml +parameters: +- name: "ob_restore_phase" + type: boolean + default: true # or false if you don't want restore phase + +steps: +- powershell: | + # script content + displayName: 'Step Name' + env: + ob_restore_phase: ${{ parameters.ob_restore_phase }} +``` + +The key is to: +1. Define `ob_restore_phase` as a **boolean** parameter +2. Set `ob_restore_phase: ${{ parameters.ob_restore_phase }}` directly in each step's `env:` block +3. Pass `true` to run in restore phase, `false` to run in normal build phase + +### ❌ Incorrect (Does Not Work) +```yaml +steps: +- powershell: | + # script content + displayName: 'Step Name' + ${{ if eq(parameters.useRestorePhase, 'yes') }}: + env: + ob_restore_phase: true +``` + +Using conditionals at the same indentation level as `env:` causes only the first step to execute in restore phase. + +## Parameters + +Templates using this pattern should accept an `ob_restore_phase` boolean parameter: + +```yaml +parameters: +- name: "ob_restore_phase" + type: boolean + default: true # Set to true to run in restore phase by default +``` + +## Reference Examples + +Working examples of this pattern can be found in: +- `.pipelines/templates/insert-nuget-config-azfeed.yml` - Demonstrates the correct pattern +- `.pipelines/templates/SetVersionVariables.yml` - Updated to use this pattern + +## Why This Matters + +The restore phase in OneBranch pipelines runs before signing and other build operations. Steps that need to: +- Set environment variables for the entire build +- Configure authentication +- Prepare the repository structure + +Must run in the restore phase to be available when subsequent stages execute. + +## Common Use Cases + +- Setting `REPOROOT` variable +- Configuring NuGet feeds with authentication +- Setting version variables +- Repository preparation and validation + +## Troubleshooting + +If only the first step in your template is running in restore phase: +1. Check that `env:` block exists for **each step** +2. Verify the conditional `${{ if ... }}:` is **inside** the `env:` block +3. Confirm indentation is correct (conditional is indented under `env:`) diff --git a/.github/instructions/onebranch-signing-configuration.instructions.md b/.github/instructions/onebranch-signing-configuration.instructions.md new file mode 100644 index 00000000000..747fcaffdd6 --- /dev/null +++ b/.github/instructions/onebranch-signing-configuration.instructions.md @@ -0,0 +1,195 @@ +--- +applyTo: + - ".pipelines/**/*.yml" + - ".pipelines/**/*.yaml" +--- + +# OneBranch Signing Configuration + +This guide explains how to configure OneBranch signing variables in Azure Pipeline jobs, particularly when signing is not required. + +## Purpose + +OneBranch pipelines include signing infrastructure by default. For build-only jobs where signing happens in a separate stage, you should disable signing setup to improve performance and avoid unnecessary overhead. + +## Disable Signing for Build-Only Jobs + +When a job does not perform signing (e.g., it only builds artifacts that will be signed in a later stage), disable both signing setup and code sign validation: + +```yaml +variables: + - name: ob_signing_setup_enabled + value: false # Disable signing setup - this is a build-only stage + - name: ob_sdl_codeSignValidation_enabled + value: false # Skip signing validation in build-only stage +``` + +### Why Disable These Variables? + +**`ob_signing_setup_enabled: false`** +- Prevents OneBranch from setting up the signing infrastructure +- Reduces job startup time +- Avoids unnecessary credential validation +- Only disable when the job will NOT sign any artifacts + +**`ob_sdl_codeSignValidation_enabled: false`** +- Skips validation that checks if files are properly signed +- Appropriate for build stages where artifacts are unsigned +- Must be enabled in signing/release stages to validate signatures + +## Common Patterns + +### Build-Only Job (No Signing) + +```yaml +jobs: +- job: build_artifacts + variables: + - name: ob_signing_setup_enabled + value: false + - name: ob_sdl_codeSignValidation_enabled + value: false + steps: + - checkout: self + - pwsh: | + # Build unsigned artifacts + Start-PSBuild +``` + +### Signing Job + +```yaml +jobs: +- job: sign_artifacts + variables: + - name: ob_signing_setup_enabled + value: true + - name: ob_sdl_codeSignValidation_enabled + value: true + steps: + - checkout: self + env: + ob_restore_phase: true # Steps before first signing operation + - pwsh: | + # Prepare artifacts for signing + env: + ob_restore_phase: true # Steps before first signing operation + - task: onebranch.pipeline.signing@1 + displayName: 'Sign artifacts' + # Signing step runs in build phase (no ob_restore_phase) + - pwsh: | + # Post-signing validation + # Post-signing steps run in build phase (no ob_restore_phase) +``` + +## Restore Phase Usage with Signing + +**The restore phase (`ob_restore_phase: true`) should only be used in jobs that perform signing operations.** It separates preparation steps from the actual signing and build steps. + +### When to Use Restore Phase + +Use `ob_restore_phase: true` **only** in jobs where `ob_signing_setup_enabled: true`: + +```yaml +jobs: +- job: sign_artifacts + variables: + - name: ob_signing_setup_enabled + value: true # Signing enabled + steps: + # Steps BEFORE first signing operation: use restore phase + - checkout: self + env: + ob_restore_phase: true + - template: prepare-for-signing.yml + parameters: + ob_restore_phase: true + + # SIGNING STEP: runs in build phase (no ob_restore_phase) + - task: onebranch.pipeline.signing@1 + displayName: 'Sign artifacts' + + # Steps AFTER signing: run in build phase (no ob_restore_phase) + - pwsh: | + # Validation or packaging +``` + +### When NOT to Use Restore Phase + +**Do not use restore phase in build-only jobs** where `ob_signing_setup_enabled: false`: + +```yaml +jobs: +- job: build_artifacts + variables: + - name: ob_signing_setup_enabled + value: false # No signing + - name: ob_sdl_codeSignValidation_enabled + value: false + steps: + - checkout: self + # NO ob_restore_phase - not needed without signing + - pwsh: | + Start-PSBuild +``` + +**Why?** The restore phase is part of OneBranch's signing infrastructure. Using it without signing enabled adds unnecessary overhead without benefit. + +## Related Variables + +Other OneBranch signing-related variables: + +- `ob_sdl_binskim_enabled`: Controls BinSkim security analysis (can be false in build-only, true in signing stages) + +## Best Practices + +1. **Separate build and signing stages**: Build artifacts in one job, sign in another +2. **Disable signing in build stages**: Improves performance and clarifies intent +3. **Only use restore phase with signing**: The restore phase should only be used in jobs where signing is enabled (`ob_signing_setup_enabled: true`) +4. **Restore phase before first signing step**: All steps before the first signing operation should use `ob_restore_phase: true` +5. **Always validate after signing**: Enable validation in signing stages to catch issues +6. **Document the reason**: Add comments explaining why signing is disabled or why restore phase is used + +## Example: Split Build and Sign Pipeline + +```yaml +stages: + - stage: Build + jobs: + - job: build_windows + variables: + - name: ob_signing_setup_enabled + value: false # Build-only, no signing + - name: ob_sdl_codeSignValidation_enabled + value: false # Artifacts are unsigned + steps: + - template: templates/build-unsigned.yml + + - stage: Sign + dependsOn: Build + jobs: + - job: sign_windows + variables: + - name: ob_signing_setup_enabled + value: true # Enable signing infrastructure + - name: ob_sdl_codeSignValidation_enabled + value: true # Validate signatures + steps: + - template: templates/sign-artifacts.yml +``` + +## Troubleshooting + +**Job fails with signing-related errors but signing is disabled:** +- Verify `ob_signing_setup_enabled: false` is set in variables +- Check that no template is overriding the setting +- Ensure `ob_sdl_codeSignValidation_enabled: false` is also set + +**Signed artifacts fail validation:** +- Confirm `ob_sdl_codeSignValidation_enabled: true` in signing job +- Verify signing actually occurred +- Check certificate configuration + +## Reference + +- PowerShell signing templates: `.pipelines/templates/packaging/windows/sign.yml` diff --git a/.github/instructions/pester-set-itresult-pattern.instructions.md b/.github/instructions/pester-set-itresult-pattern.instructions.md new file mode 100644 index 00000000000..33a73ca081d --- /dev/null +++ b/.github/instructions/pester-set-itresult-pattern.instructions.md @@ -0,0 +1,198 @@ +--- +applyTo: + - "**/*.Tests.ps1" +--- + +# Pester Set-ItResult Pattern for Pending and Skipped Tests + +## Purpose + +This instruction explains when and how to use `Set-ItResult` in Pester tests to mark tests as Pending or Skipped dynamically within test execution. + +## When to Use Set-ItResult + +Use `Set-ItResult` when you need to conditionally mark a test as Pending or Skipped based on runtime conditions that can't be determined at test definition time. + +### Pending vs Skipped + +**Pending**: Use for tests that should be enabled but temporarily can't run due to: +- Intermittent external service failures (network, APIs) +- Known bugs being fixed +- Missing features being implemented +- Environmental issues that are being resolved + +**Skipped**: Use for tests that aren't applicable to the current environment: +- Platform-specific tests running on wrong platform +- Tests requiring specific hardware/configuration not present +- Tests requiring elevated permissions when not available +- Feature-specific tests when feature is disabled + +## Pattern + +### Basic Usage + +```powershell +It "Test description" { + if ($shouldBePending) { + Set-ItResult -Pending -Because "Explanation of why test is pending" + return + } + + if ($shouldBeSkipped) { + Set-ItResult -Skipped -Because "Explanation of why test is skipped" + return + } + + # Test code here +} +``` + +### Important: Always Return After Set-ItResult + +After calling `Set-ItResult`, you **must** return from the test to prevent further execution: + +```powershell +It "Test that checks environment" { + if ($env:SKIP_TESTS -eq 'true') { + Set-ItResult -Skipped -Because "SKIP_TESTS environment variable is set" + return # This is required! + } + + # Test assertions + $result | Should -Be $expected +} +``` + +**Why?** Without `return`, the test continues executing and may fail with errors unrelated to the pending/skipped condition. + +## Examples from the Codebase + +### Example 1: Pending for Intermittent Network Issues + +```powershell +It "Validate Update-Help for module" { + if ($markAsPending) { + Set-ItResult -Pending -Because "Update-Help from the web has intermittent connectivity issues. See issues #2807 and #6541." + return + } + + Update-Help -Module $moduleName -Force + # validation code... +} +``` + +### Example 2: Skipped for Missing Environment + +```powershell +It "Test requires CI environment" { + if (-not $env:CI) { + Set-ItResult -Skipped -Because "Test requires CI environment to safely install Pester" + return + } + + Install-CIPester -ErrorAction Stop +} +``` + +### Example 3: Pending for Platform-Specific Issue + +```powershell +It "Clear-Host works correctly" { + if ($IsARM64) { + Set-ItResult -Pending -Because "ARM64 runs in non-interactively mode and Clear-Host does not work." + return + } + + & { Clear-Host; 'hi' } | Should -BeExactly 'hi' +} +``` + +### Example 4: Skipped for Missing Feature + +```powershell +It "Test ACR authentication" { + if ($env:ACRTESTS -ne 'true') { + Set-ItResult -Skipped -Because "The tests require the ACRTESTS environment variable to be set to 'true' for ACR authentication." + return + } + + $psgetModuleInfo = Find-PSResource -Name $ACRTestModule -Repository $ACRRepositoryName + # test assertions... +} +``` + +## Alternative: Static -Skip and -Pending Parameters + +For conditions that can be determined at test definition time, use the static parameters instead: + +```powershell +# Static skip - condition known at definition time +It "Windows-only test" -Skip:(-not $IsWindows) { + # test code +} + +# Static pending - always pending +It "Test for feature being implemented" -Pending { + # test code that will fail until feature is done +} +``` + +**Use Set-ItResult when**: +- Condition depends on runtime state +- Condition is determined inside a helper function +- Need to check multiple conditions sequentially + +**Use static parameters when**: +- Condition is known at test definition +- Condition doesn't change during test run +- Want Pester to show the condition in test discovery + +## Best Practices + +1. **Always include -Because parameter** with a clear explanation +2. **Always return after Set-ItResult** to prevent further execution +3. **Reference issues or documentation** when relevant (e.g., "See issue #1234") +4. **Be specific in the reason** - explain what's wrong and what's needed +5. **Use Pending sparingly** - it indicates a problem that should be fixed +6. **Prefer Skipped over Pending** when test truly isn't applicable + +## Common Mistakes + +### ❌ Mistake 1: Forgetting to Return + +```powershell +It "Test" { + if ($condition) { + Set-ItResult -Pending -Because "Reason" + # Missing return - test code will still execute! + } + $value | Should -Be $expected # This runs and fails +} +``` + +### ❌ Mistake 2: Vague Reason + +```powershell +Set-ItResult -Pending -Because "Doesn't work" # Too vague +``` + +### ✅ Correct: + +```powershell +It "Test" { + if ($condition) { + Set-ItResult -Pending -Because "Update-Help has intermittent network timeouts. See issue #2807." + return + } + $value | Should -Be $expected +} +``` + +## See Also + +- [Pester Documentation: Set-ItResult](https://pester.dev/docs/commands/Set-ItResult) +- [Pester Documentation: It](https://pester.dev/docs/commands/It) +- Examples in the codebase: + - `test/powershell/Host/ConsoleHost.Tests.ps1` + - `test/infrastructure/ciModule.Tests.ps1` + - `tools/packaging/releaseTests/sbom.tests.ps1` diff --git a/.github/instructions/pester-test-status-and-working-meaning.instructions.md b/.github/instructions/pester-test-status-and-working-meaning.instructions.md new file mode 100644 index 00000000000..d2b28a05f18 --- /dev/null +++ b/.github/instructions/pester-test-status-and-working-meaning.instructions.md @@ -0,0 +1,299 @@ +--- +applyTo: "**/*.Tests.ps1" +--- + +# Pester Test Status Meanings and Working Tests + +## Purpose + +This guide clarifies Pester test outcomes and what it means for a test to be "working" - which requires both **passing** AND **actually validating functionality**. + +## Test Statuses in Pester + +### Passed ✓ +**Status Code**: `Passed` +**Exit Result**: Test ran successfully, all assertions passed + +**What it means**: +- Test executed without errors +- All `Should` statements evaluated to true +- Test setup and teardown completed without issues +- Test is **validating** the intended functionality + +**What it does NOT mean**: +- The feature is working (assertions could be wrong) +- The test is meaningful (could be testing wrong thing) +- The test exercises all code paths + +### Failed ✗ +**Status Code**: `Failed` +**Exit Result**: Test ran but assertions failed + +**What it means**: +- Test executed but an assertion returned false +- Expected value did not match actual value +- Test detected a problem with the functionality + +**Examples**: +``` +Expected $true but got $false +Expected 5 items but got 3 +Expected no error but got: Cannot find parameter +``` + +### Error ⚠ +**Status Code**: `Error` +**Exit Result**: Test crashed with an exception + +**What it means**: +- Test failed to complete +- An exception was thrown during test execution +- Could be in test setup, test body, or test cleanup +- Often indicates environmental issue, not code functional issue + +**Examples**: +``` +Cannot bind argument to parameter 'Path' because it is null +File not found: C:\expected\config.json +Access denied writing to registry +``` + +### Pending ⏳ +**Status Code**: `Pending` +**Exit Result**: Test ran but never completed assertions + +**What it means**: +- Test was explicitly marked as not ready to run +- `Set-ItResult -Pending` was called +- Used to indicate: known bugs, missing features, environmental issues + +**When to use Pending**: +- Test for feature in development +- Test disabled due to known bug (issue #1234) +- Test disabled due to intermittent failures being fixed +- Platform-specific issues being resolved + +**⚠️ WARNING**: Pending tests are NOT validating functionality. They hide problems. + +### Skipped ⊘ +**Status Code**: `Skipped` +**Exit Result**: Test did not run (detected at start) + +**What it means**: +- Test was intentionally not executed +- `-Skip` parameter or `It -Skip:$condition` was used +- Environment doesn't support this test + +**When to use Skip**: +- Test not applicable to current platform (Windows-only test on Linux) +- Test requires feature that's not available (admin privileges) +- Test requires specific configuration not present + +**Difference from Pending**: +- **Skip**: "This test shouldn't run here" (known upfront) +- **Pending**: "This test should eventually run but can't now" + +### Ignored ✛ +**Status Code**: `Ignored` +**Exit Result**: Test marked as not applicable + +**What it means**: +- Test has `[Ignore("reason")]` attribute +- Test is permanently disabled in this location +- Not the same as Skipped (which is conditional) + +**When to use Ignore**: +- Test for deprecated feature +- Test for bug that won't be fixed +- Test moved to different test file + +--- + +## What Does "Working" Actually Mean? + +A test is **working** when it meets BOTH criteria: + +### 1. **Test Status is PASSED** ✓ +```powershell +It "Test name" { + # Test executes + # All assertions pass + # Returns Passed status +} +``` + +### 2. **Test Actually Validates Functionality** +```powershell +# ✓ GOOD: Tests actual functionality +It "Get-Item returns files from directory" -Tags @('Unit') { + $testDir = New-Item -ItemType Directory -Force + New-Item -Path $testDir -Name "file.txt" -ItemType File | Out-Null + + $result = Get-Item -Path "$testDir\file.txt" + + $result.Name | Should -Be "file.txt" + $result | Should -Exist + + Remove-Item $testDir -Recurse -Force +} + +# ✗ BAD: Returns Passed but doesn't validate functionality +It "Get-Item returns files from directory" -Tags @('Unit') { + $result = Get-Item -Path somepath # May not exist, may not actually test + $result | Should -Not -BeNullOrEmpty # Too vague +} + +# ✗ BAD: Test marked Pending - validation is hidden +It "Get-Item returns files from directory" -Tags @('Unit') { + Set-ItResult -Pending -Because "File system not working" + return + # No validation happens at all +} +``` + +--- + +## The Problem with Pending Tests + +### Why Pending Tests Hide Problems + +```powershell +# BAD: Test marked Pending - looks like "working" status but validation is skipped +It "Download help from web" { + Set-ItResult -Pending -Because "Web connectivity issues" + return + + # This code never runs: + Update-Help -Module PackageManagement -Force -ErrorAction Stop + Get-Help Get-Package | Should -Not -BeNullOrEmpty +} +``` + +**Result**: +- ✗ Feature is broken (Update-Help fails) +- ✓ Test shows "Pending" (looks acceptable) +- ✗ Problem is hidden and never fixed + +### The Right Approach + +**Option A: Fix the root cause** +```powershell +It "Download help from web" { + # Use local assets that are guaranteed to work + Update-Help -Module PackageManagement -SourcePath ./assets -Force -ErrorAction Stop + + Get-Help Get-Package | Should -Not -BeNullOrEmpty +} +``` + +**Option B: Gracefully skip when unavailable** +```powershell +It "Download help from web" -Skip:$(-not $hasInternet) { + Update-Help -Module PackageManagement -Force -ErrorAction Stop + Get-Help Get-Package | Should -Not -BeNullOrEmpty +} +``` + +**Option C: Add retry logic for intermittent issues** +```powershell +It "Download help from web" { + $maxRetries = 3 + $attempt = 0 + + while ($attempt -lt $maxRetries) { + try { + Update-Help -Module PackageManagement -Force -ErrorAction Stop + break + } + catch { + $attempt++ + if ($attempt -ge $maxRetries) { throw } + Start-Sleep -Seconds 2 + } + } + + Get-Help Get-Package | Should -Not -BeNullOrEmpty +} +``` + +--- + +## Test Status Summary Table + +| Status | Passed? | Validates? | Counts as "Working"? | Use When | +|--------|---------|------------|----------------------|----------| +| **Passed** | ✓ | ✓ | **YES** | Feature is working and test proves it | +| **Failed** | ✗ | ✓ | NO | Feature is broken or test has wrong expectation | +| **Error** | ✗ | ✗ | NO | Test infrastructure broken, can't validate | +| **Pending** | - | ✗ | **NO** ⚠️ | Temporary - test should eventually pass | +| **Skipped** | - | ✗ | NO | Test not applicable to this environment | +| **Ignored** | - | ✗ | NO | Test permanently disabled | + +--- + +## Recommended Patterns + +### Pattern 1: Resilient Test with Fallback +```powershell +It "Feature works with web or local source" { + $useLocal = $false + + try { + Update-Help -Module Package -Force -ErrorAction Stop + } + catch { + $useLocal = $true + Update-Help -Module Package -SourcePath ./assets -Force -ErrorAction Stop + } + + # Validate functionality regardless of source + Get-Help Get-Package | Should -Not -BeNullOrEmpty +} +``` + +### Pattern 2: Conditional Skip with Clear Reason +```powershell +Describe "Update-Help from Web" -Skip $(-not (Test-InternetConnectivity)) { + It "Downloads help successfully" { + Update-Help -Module PackageManagement -Force -ErrorAction Stop + Get-Help Get-Package | Should -Not -BeNullOrEmpty + } +} +``` + +### Pattern 3: Separate Suites by Dependency +```powershell +Describe "Help Content Tests - Web" { + # Tests that require internet - can be skipped if unavailable + It "Downloads from web" { ... } +} + +Describe "Help Content Tests - Local" { + # Tests with local assets - should always pass + It "Loads from local assets" { + Update-Help -Module Package -SourcePath ./assets -Force + Get-Help Get-Package | Should -Not -BeNullOrEmpty + } +} +``` + +--- + +## Checklist: Is Your Test "Working"? + +- [ ] Test status is **Passed** (not Pending, not Skipped, not Failed) +- [ ] Test actually **executes** the feature being tested +- [ ] Test has **specific assertions** (not just `Should -Not -BeNullOrEmpty`) +- [ ] Test includes **cleanup** (removes temp files, restores state) +- [ ] Test can run **multiple times** without side effects +- [ ] Test failure **indicates a real problem** (not flaky assertions) +- [ ] Test success **proves the feature works** (not just "didn't crash") + +If any of these is false, your test may be passing but not "working" properly. + +--- + +## See Also + +- [Pester Documentation](https://pester.dev/) +- [Set-ItResult Documentation](https://pester.dev/docs/commands/Set-ItResult) diff --git a/.github/instructions/powershell-automatic-variables.instructions.md b/.github/instructions/powershell-automatic-variables.instructions.md new file mode 100644 index 00000000000..5015847f41f --- /dev/null +++ b/.github/instructions/powershell-automatic-variables.instructions.md @@ -0,0 +1,159 @@ +--- +applyTo: + - "**/*.ps1" + - "**/*.psm1" +--- + +# PowerShell Automatic Variables - Naming Guidelines + +## Purpose + +This instruction provides guidelines for avoiding conflicts with PowerShell's automatic variables when writing PowerShell scripts and modules. + +## What Are Automatic Variables? + +PowerShell has built-in automatic variables that are created and maintained by PowerShell itself. Assigning values to these variables can cause unexpected behavior and side effects. + +## Common Automatic Variables to Avoid + +### Critical Variables (Never Use) + +- **`$matches`** - Contains the results of regular expression matches. Overwriting this can break regex operations. +- **`$_`** - Represents the current object in the pipeline. Only use within pipeline blocks. +- **`$PSItem`** - Alias for `$_`. Same rules apply. +- **`$args`** - Contains an array of undeclared parameters. Don't use as a regular variable. +- **`$input`** - Contains an enumerator of all input passed to a function. Don't reassign. +- **`$LastExitCode`** - Exit code of the last native command. Don't overwrite unless intentional. +- **`$?`** - Success status of the last command. Don't use as a variable name. +- **`$$`** - Last token in the last line received by the session. Don't use. +- **`$^`** - First token in the last line received by the session. Don't use. + +### Context Variables (Use with Caution) + +- **`$Error`** - Array of error objects. Don't replace, but can modify (e.g., `$Error.Clear()`). +- **`$PSBoundParameters`** - Parameters passed to the current function. Read-only. +- **`$MyInvocation`** - Information about the current command. Read-only. +- **`$PSCmdlet`** - Cmdlet object for advanced functions. Read-only. + +### Other Common Automatic Variables + +- `$true`, `$false`, `$null` - Boolean and null constants +- `$HOME`, `$PSHome`, `$PWD` - Path-related variables +- `$PID` - Process ID of the current PowerShell session +- `$Host` - Host application object +- `$PSVersionTable` - PowerShell version information + +For a complete list, see: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_automatic_variables + +## Best Practices + +### ❌ Bad - Using Automatic Variable Names + +```powershell +# Bad: $matches is an automatic variable used for regex capture groups +$matches = Select-String -Path $file -Pattern $pattern + +# Bad: $args is an automatic variable for undeclared parameters +$args = Get-ChildItem + +# Bad: $input is an automatic variable for pipeline input +$input = Read-Host "Enter value" +``` + +### ✅ Good - Using Descriptive Alternative Names + +```powershell +# Good: Use descriptive names that avoid conflicts +$matchedLines = Select-String -Path $file -Pattern $pattern + +# Good: Use specific names for arguments +$arguments = Get-ChildItem + +# Good: Use specific names for user input +$userInput = Read-Host "Enter value" +``` + +## Naming Alternatives + +When you encounter a situation where you might use an automatic variable name, use these alternatives: + +| Avoid | Use Instead | +|-------|-------------| +| `$matches` | `$matchedLines`, `$matchResults`, `$regexMatches` | +| `$args` | `$arguments`, `$parameters`, `$commandArgs` | +| `$input` | `$userInput`, `$inputValue`, `$inputData` | +| `$_` (outside pipeline) | Use a named parameter or explicit variable | +| `$Error` (reassignment) | Don't reassign; use `$Error.Clear()` if needed | + +## How to Check + +### PSScriptAnalyzer Rule + +PSScriptAnalyzer has a built-in rule that detects assignments to automatic variables: + +```powershell +# This will trigger PSAvoidAssignmentToAutomaticVariable +$matches = Get-Something +``` + +**Rule ID**: PSAvoidAssignmentToAutomaticVariable + +### Manual Review + +When writing PowerShell code, always: +1. Avoid variable names that match PowerShell keywords or automatic variables +2. Use descriptive, specific names that clearly indicate the variable's purpose +3. Run PSScriptAnalyzer on your code before committing +4. Review code for variable naming during PR reviews + +## Examples from the Codebase + +### Example 1: Regex Matching + +```powershell +# ❌ Bad - Overwrites automatic $matches variable +$matches = [regex]::Matches($content, $pattern) + +# ✅ Good - Uses descriptive name +$regexMatches = [regex]::Matches($content, $pattern) +``` + +### Example 2: Select-String Results + +```powershell +# ❌ Bad - Conflicts with automatic $matches +$matches = Select-String -Path $file -Pattern $pattern + +# ✅ Good - Clear and specific +$matchedLines = Select-String -Path $file -Pattern $pattern +``` + +### Example 3: Collecting Arguments + +```powershell +# ❌ Bad - Conflicts with automatic $args +function Process-Items { + $args = $MyItems + # ... process items +} + +# ✅ Good - Descriptive parameter name +function Process-Items { + [CmdletBinding()] + param( + [Parameter(ValueFromRemainingArguments)] + [string[]]$Items + ) + # ... process items +} +``` + +## References + +- [PowerShell Automatic Variables Documentation](https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_automatic_variables) +- [PSScriptAnalyzer Rules](https://github.com/PowerShell/PSScriptAnalyzer/blob/master/docs/Rules/README.md) +- [PowerShell Best Practices](https://learn.microsoft.com/powershell/scripting/developer/cmdlet/strongly-encouraged-development-guidelines) + +## Summary + +**Key Takeaway**: Always use descriptive, specific variable names that clearly indicate their purpose and avoid conflicts with PowerShell's automatic variables. When in doubt, choose a longer, more descriptive name over a short one that might conflict. diff --git a/.github/instructions/powershell-module-organization.instructions.md b/.github/instructions/powershell-module-organization.instructions.md new file mode 100644 index 00000000000..9cdba06c364 --- /dev/null +++ b/.github/instructions/powershell-module-organization.instructions.md @@ -0,0 +1,201 @@ +--- +applyTo: + - "tools/ci.psm1" + - "build.psm1" + - "tools/packaging/**/*.psm1" + - ".github/**/*.yml" + - ".github/**/*.yaml" +--- + +# Guidelines for PowerShell Code Organization + +## When to Move Code from YAML to PowerShell Modules + +PowerShell code in GitHub Actions YAML files should be kept minimal. Move code to a module when: + +### Size Threshold +- **More than ~30 lines** of PowerShell in a YAML file step +- **Any use of .NET types** like `[regex]`, `[System.IO.Path]`, etc. +- **Complex logic** requiring multiple nested loops or conditionals +- **Reusable functionality** that might be needed elsewhere + +### Indicators to Move Code +1. Using .NET type accelerators (`[regex]`, `[PSCustomObject]`, etc.) +2. Complex string manipulation or parsing +3. File system operations beyond basic reads/writes +4. Logic that would benefit from unit testing +5. Code that's difficult to read/maintain in YAML format + +## Which Module to Use + +### ci.psm1 (`tools/ci.psm1`) +**Purpose**: CI/CD-specific operations and workflows + +**Use for**: +- Build orchestration (invoking builds, tests, packaging) +- CI environment setup and configuration +- Test execution and result processing +- Artifact handling and publishing +- CI-specific validations and checks +- Environment variable management for CI + +**Examples**: +- `Invoke-CIBuild` - Orchestrates build process +- `Invoke-CITest` - Runs Pester tests +- `Test-MergeConflictMarker` - Validates files for conflicts +- `Set-BuildVariable` - Manages CI variables + +**When NOT to use**: +- Core build operations (use build.psm1) +- Package creation logic (use packaging.psm1) +- Platform-specific build steps + +### build.psm1 (`build.psm1`) +**Purpose**: Core build operations and utilities + +**Use for**: +- Compiling source code +- Resource generation +- Build configuration management +- Core build utilities (New-PSOptions, Get-PSOutput, etc.) +- Bootstrap operations +- Cross-platform build helpers + +**Examples**: +- `Start-PSBuild` - Main build function +- `Start-PSBootstrap` - Bootstrap dependencies +- `New-PSOptions` - Create build configuration +- `Start-ResGen` - Generate resources + +**When NOT to use**: +- CI workflow orchestration (use ci.psm1) +- Package creation (use packaging.psm1) +- Test execution + +### packaging.psm1 (`tools/packaging/packaging.psm1`) +**Purpose**: Package creation and distribution + +**Use for**: +- Creating distribution packages (MSI, RPM, DEB, etc.) +- Package-specific metadata generation +- Package signing operations +- Platform-specific packaging logic + +**Examples**: +- `Start-PSPackage` - Create packages +- `New-MSIXPackage` - Create Windows MSIX +- `New-DotnetSdkContainerFxdPackage` - Create container packages + +**When NOT to use**: +- Building binaries (use build.psm1) +- Running tests (use ci.psm1) +- General utilities + +## Best Practices + +### Keep YAML Minimal +```yaml +# ❌ Bad - too much logic in YAML +- name: Check files + shell: pwsh + run: | + $files = Get-ChildItem -Recurse + foreach ($file in $files) { + $content = Get-Content $file -Raw + if ($content -match $pattern) { + # ... complex processing ... + } + } + +# ✅ Good - call function from module +- name: Check files + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Test-SomeCondition -Path ${{ github.workspace }} +``` + +### Document Functions +Always include comment-based help for functions: +```powershell +function Test-MyFunction +{ + <# + .SYNOPSIS + Brief description + .DESCRIPTION + Detailed description + .PARAMETER ParameterName + Parameter description + .EXAMPLE + Test-MyFunction -ParameterName Value + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [string] $ParameterName + ) + # Implementation +} +``` + +### Error Handling +Use proper error handling in modules: +```powershell +try { + # Operation +} +catch { + Write-Error "Detailed error message: $_" + throw +} +``` + +### Verbose Output +Use `Write-Verbose` for debugging information: +```powershell +Write-Verbose "Processing file: $filePath" +``` + +## Module Dependencies + +- **ci.psm1** imports both `build.psm1` and `packaging.psm1` +- **build.psm1** is standalone (minimal dependencies) +- **packaging.psm1** imports `build.psm1` + +When adding new functions, consider these import relationships to avoid circular dependencies. + +## Testing Modules + +Functions in modules should be testable: +```powershell +# Test locally +Import-Module ./tools/ci.psm1 -Force +Test-MyFunction -Parameter Value + +# Can be unit tested with Pester +Describe "Test-MyFunction" { + It "Should return expected result" { + # Test implementation + } +} +``` + +## Migration Checklist + +When moving code from YAML to a module: + +1. ✅ Determine which module is appropriate (ci, build, or packaging) +2. ✅ Create function with proper parameter validation +3. ✅ Add comment-based help documentation +4. ✅ Use `[CmdletBinding()]` for advanced function features +5. ✅ Include error handling +6. ✅ Add verbose output for debugging +7. ✅ Test the function independently +8. ✅ Update YAML to call the new function +9. ✅ Verify the workflow still works end-to-end + +## References + +- PowerShell Advanced Functions: https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_functions_advanced +- Comment-Based Help: https://learn.microsoft.com/powershell/scripting/developer/help/writing-help-for-windows-powershell-scripts-and-functions diff --git a/.github/instructions/powershell-parameter-naming.instructions.md b/.github/instructions/powershell-parameter-naming.instructions.md new file mode 100644 index 00000000000..155fd1a85c3 --- /dev/null +++ b/.github/instructions/powershell-parameter-naming.instructions.md @@ -0,0 +1,69 @@ +--- +applyTo: '**/*.ps1, **/*.psm1' +description: Naming conventions for PowerShell parameters +--- + +# PowerShell Parameter Naming Conventions + +## Purpose + +This instruction defines the naming conventions for parameters in PowerShell scripts and modules. Consistent parameter naming improves code readability, maintainability, and usability for users of PowerShell cmdlets and functions. + +## Parameter Naming Rules + +### General Conventions +- **Singular Nouns**: Use singular nouns for parameter names even if the parameter is expected to handle multiple values (e.g., `File` instead of `Files`). +- **Use PascalCase**: Parameter names must use PascalCase (e.g., `ParameterName`). +- **Descriptive Names**: Parameter names should be descriptive and convey their purpose clearly (e.g., `FilePath`, `UserName`). +- **Avoid Abbreviations**: Avoid using abbreviations unless they are widely recognized (e.g., `ID` for Identifier). +- **Avoid Reserved Words**: Do not use PowerShell reserved words as parameter names (e.g., `if`, `else`, `function`). + +### Units and Precision +- **Include Units in Parameter Names**: When a parameter represents a value with units, include the unit in the parameter name for clarity: + - `TimeoutSec` instead of `Timeout` + - `RetryIntervalSec` instead of `RetryInterval` + - `MaxSizeBytes` instead of `MaxSize` +- **Use Full Words for Clarity**: Spell out common terms to match PowerShell conventions: + - `MaximumRetryCount` instead of `MaxRetries` + - `MinimumLength` instead of `MinLength` + +### Alignment with Built-in Cmdlets +- **Follow Existing PowerShell Conventions**: When your parameter serves a similar purpose to a built-in cmdlet parameter, use the same or similar naming: + - Match `Invoke-WebRequest` parameters when making HTTP requests: `TimeoutSec`, `MaximumRetryCount`, `RetryIntervalSec` + - Follow common parameter patterns like `Path`, `Force`, `Recurse`, `WhatIf`, `Confirm` +- **Consistency Within Scripts**: If multiple parameters relate to the same concept, use consistent naming patterns (e.g., `TimeoutSec`, `RetryIntervalSec` both use `Sec` suffix). + +## Examples + +### Good Parameter Names +```powershell +param( + [string[]]$File, # Singular, even though it accepts arrays + [int]$TimeoutSec = 30, # Unit included + [int]$MaximumRetryCount = 2, # Full word "Maximum" + [int]$RetryIntervalSec = 2, # Consistent with TimeoutSec + [string]$Path, # Standard PowerShell convention + [switch]$Force # Common PowerShell parameter +) +``` + +### Names to Avoid +```powershell +param( + [string[]]$Files, # Should be singular: File + [int]$Timeout = 30, # Missing unit: TimeoutSec + [int]$MaxRetries = 2, # Should be: MaximumRetryCount + [int]$RetryInterval = 2, # Missing unit: RetryIntervalSec + [string]$FileLoc, # Avoid abbreviations: FilePath + [int]$Max # Ambiguous: MaximumWhat? +) +``` + +## Exceptions +- **Common Terms**: Some common terms may be used in plural form if they are widely accepted in the context (e.g., `Credentials`, `Permissions`). +- **Legacy Code**: Existing code that does not follow these conventions may be exempted to avoid breaking changes, but new code should adhere to these guidelines. +- **Well Established Naming Patterns**: If a naming pattern is well established in the PowerShell community, it may be used even if it does not strictly adhere to these guidelines. + +## References +- [PowerShell Cmdlet Design Guidelines](https://learn.microsoft.com/powershell/scripting/developer/cmdlet/strongly-encouraged-development-guidelines) +- [About Parameters - PowerShell Documentation](https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_parameters) diff --git a/.github/instructions/publishing-pester-result.instructions.md b/.github/instructions/publishing-pester-result.instructions.md new file mode 100644 index 00000000000..49010e65a99 --- /dev/null +++ b/.github/instructions/publishing-pester-result.instructions.md @@ -0,0 +1,272 @@ +--- +applyTo: ".github/**/*.{yml,yaml}" +--- + +# Publishing Pester Test Results Instructions + +This document describes how the PowerShell repository uses GitHub Actions to publish Pester test results. + +## Overview + +The PowerShell repository uses a custom composite GitHub Action located at `.github/actions/test/process-pester-results` to process and publish Pester test results in CI/CD workflows. +This action aggregates test results from NUnitXml formatted files, creates a summary in the GitHub Actions job summary, and uploads the results as artifacts. + +## How It Works + +### Action Location and Structure + +**Path**: `.github/actions/test/process-pester-results/` + +The action consists of two main files: + +1. **action.yml** - The composite action definition +1. **process-pester-results.ps1** - PowerShell script that processes test results + +### Action Inputs + +The action accepts the following inputs: + +- **name** (required): A descriptive name for the test run (e.g., "UnelevatedPesterTests-CI") + - Used for naming the uploaded artifact and in the summary + - Format: `junit-pester-{name}` + +- **testResultsFolder** (optional): Path to the folder containing test result XML files + - Default: `${{ runner.workspace }}/testResults` + - The script searches for all `*.xml` files in this folder recursively + +### Action Workflow + +The action performs the following steps: + +1. **Process Test Results** + - Runs `process-pester-results.ps1` with the provided name and test results folder + - Parses all NUnitXml formatted test result files (`*.xml`) + - Aggregates test statistics across all files: + - Total test cases + - Errors + - Failures + - Not run tests + - Inconclusive tests + - Ignored tests + - Skipped tests + - Invalid tests + +1. **Generate Summary** + - Creates a markdown summary using the `$GITHUB_STEP_SUMMARY` environment variable + - Uses `Write-Log` and `Write-LogGroupStart`/`Write-LogGroupEnd` functions from `build.psm1` + - Outputs a formatted summary with all test statistics + - Example format: + + ```markdown + # Summary of {Name} + + - Total Tests: X + - Total Errors: X + - Total Failures: X + - Total Not Run: X + - Total Inconclusive: X + - Total Ignored: X + - Total Skipped: X + - Total Invalid: X + ``` + +1. **Upload Artifacts** + - Uses `actions/upload-artifact@v4` to upload test results + - Artifact name: `junit-pester-{name}` + - Always runs (even if previous steps fail) via `if: always()` + - Uploads the entire test results folder + +1. **Exit Status** + - Fails the job (exit 1) if: + - Any test errors occurred (`$testErrorCount -gt 0`) + - Any test failures occurred (`$testFailureCount -gt 0`) + - No test cases were run (`$testCaseCount -eq 0`) + +## Usage in Test Actions + +The `process-pester-results` action is called by two platform-specific composite test actions: + +### Linux/macOS Tests: `.github/actions/test/nix` + +Used in: + +- `.github/workflows/linux-ci.yml` +- `.github/workflows/macos-ci.yml` + +Example usage (lines 99-104 in `nix/action.yml`): + +```yaml +- name: Convert, Publish, and Upload Pester Test Results + uses: "./.github/actions/test/process-pester-results" + with: + name: "${{ inputs.purpose }}-${{ inputs.tagSet }}" + testResultsFolder: "${{ runner.workspace }}/testResults" +``` + +### Windows Tests: `.github/actions/test/windows` + +Used in: + +- `.github/workflows/windows-ci.yml` + +Example usage (line 78-83 in `windows/action.yml`): + +```yaml +- name: Convert, Publish, and Upload Pester Test Results + uses: "./.github/actions/test/process-pester-results" + with: + name: "${{ inputs.purpose }}-${{ inputs.tagSet }}" + testResultsFolder: ${{ runner.workspace }}\testResults +``` + +## Workflow Integration + +The process-pester-results action is integrated into the CI workflows through a multi-level hierarchy: + +### Level 1: Main CI Workflows + +- `linux-ci.yml` +- `macos-ci.yml` +- `windows-ci.yml` + +### Level 2: Test Jobs + +Each workflow contains multiple test jobs with different purposes and tag sets: + +- `UnelevatedPesterTests` with tagSet `CI` +- `ElevatedPesterTests` with tagSet `CI` +- `UnelevatedPesterTests` with tagSet `Others` +- `ElevatedPesterTests` with tagSet `Others` + +### Level 3: Platform Test Actions + +Test jobs use platform-specific actions: + +- `nix` for Linux and macOS +- `windows` for Windows + +### Level 4: Process Results Action + +Platform actions call `process-pester-results` to publish results + +## Test Execution Flow + +1. **Build Phase**: Source code is built (e.g., in `ci_build` job) +1. **Test Preparation**: + - Build artifacts are downloaded + - PowerShell is bootstrapped + - Test binaries are extracted +1. **Test Execution**: + - `Invoke-CITest` is called with: + - `-Purpose`: Test purpose (e.g., "UnelevatedPesterTests") + - `-TagSet`: Test category (e.g., "CI", "Others") + - `-OutputFormat NUnitXml`: Results format + - Results are written to `${{ runner.workspace }}/testResults` +1. **Results Processing**: + - `process-pester-results` action runs + - Results are aggregated and summarized + - Artifacts are uploaded + - Job fails if any tests failed or errored + +## Key Dependencies + +### PowerShell Modules + +- **build.psm1**: Provides utility functions + - `Write-Log`: Logging function with GitHub Actions support + - `Write-LogGroupStart`: Creates collapsible log groups + - `Write-LogGroupEnd`: Closes collapsible log groups + +### GitHub Actions Features + +- **GITHUB_STEP_SUMMARY**: Environment variable for job summary +- **actions/upload-artifact@v4**: For uploading test results +- **Composite Actions**: For reusable workflow steps + +### Test Result Format + +- **NUnitXml**: XML format for test results +- Expected XML structure with `test-results` root element containing: + - `total`: Total number of tests + - `errors`: Number of errors + - `failures`: Number of failures + - `not-run`: Number of tests not run + - `inconclusive`: Number of inconclusive tests + - `ignored`: Number of ignored tests + - `skipped`: Number of skipped tests + - `invalid`: Number of invalid tests + +## Best Practices + +1. **Naming Convention**: Use descriptive names that include both purpose and tagSet: + - Format: `{purpose}-{tagSet}` + - Example: `UnelevatedPesterTests-CI` + +1. **Test Results Location**: + - Default location: `${{ runner.workspace }}/testResults` + - Use platform-appropriate path separators (Windows: `\`, Unix: `/`) + +1. **Always Upload**: The artifact upload step uses `if: always()` to ensure results are uploaded even when tests fail + +1. **Error Handling**: The action will fail the job if: + - Tests have errors or failures (intentional fail-fast behavior) + - No tests were executed (potential configuration issue) + - `GITHUB_STEP_SUMMARY` is not set (environment issue) + +## Customizing for Your Repository + +To use this pattern in another repository: + +1. **Copy the Action Files**: + - Copy `.github/actions/test/process-pester-results/` directory + - Ensure the PowerShell script has proper permissions + +1. **Adjust Dependencies**: + - Modify or remove the `Import-Module "$PSScriptRoot/../../../../build.psm1"` line + - Implement equivalent `Write-Log` and `Write-LogGroup*` functions if needed + +1. **Customize Summary Format**: + - Modify the here-string in `process-pester-results.ps1` to change summary format + - Add additional metrics or formatting as needed + +1. **Call from Your Workflows**: + + ```yaml + - name: Process Test Results + uses: "./.github/actions/test/process-pester-results" + with: + name: "my-test-run" + testResultsFolder: "path/to/results" + ``` + +## Related Documentation + +- [GitHub Actions: Creating composite actions](https://docs.github.com/en/actions/creating-actions/creating-a-composite-action) +- [GitHub Actions: Job summaries](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary) +- [GitHub Actions: Uploading artifacts](https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts) +- [Pester: PowerShell testing framework](https://pester.dev/) +- [NUnit XML Format](https://docs.nunit.org/articles/nunit/technical-notes/usage/Test-Result-XML-Format.html) + +## Troubleshooting + +### No Test Results Found + +- Verify `testResultsFolder` path is correct +- Ensure tests are generating NUnitXml formatted output +- Check that `*.xml` files exist in the specified folder + +### Action Fails with "GITHUB_STEP_SUMMARY is not set" + +- Ensure the action runs within a GitHub Actions environment +- Cannot be run locally without mocking this environment variable + +### All Tests Pass but Job Fails + +- Check if any tests are marked as errors (different from failures) +- Verify that at least some tests executed (`$testCaseCount -eq 0`) + +### Artifact Upload Fails + +- Check artifact name for invalid characters +- Ensure the test results folder exists +- Verify actions/upload-artifact version compatibility diff --git a/.github/instructions/script-module-file-format.instructions.md b/.github/instructions/script-module-file-format.instructions.md new file mode 100644 index 00000000000..922e0d4aa31 --- /dev/null +++ b/.github/instructions/script-module-file-format.instructions.md @@ -0,0 +1,27 @@ +--- +applyTo: + - "**/*.ps1" + - "**/*.psm1" +--- + +# Script and Module File Format + +These instructions define required file-level formatting for PowerShell scripts and module files in this repository. + +## Copyright Header + +If a change adds a new `.ps1` file or `.psm1` file or touches an existing one, the file should start with the copyright and license header and have an empty line after it, as shown in the example below: + +```powershell +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +``` + +Do not place blank lines, comments, or code before this header. + +## Requirements + +- Add the copyright header when creating a new `.ps1` or `.psm1` file. +- Preserve the header when editing an existing `.ps1` or `.psm1` file. +- If an existing `.ps1` or `.psm1` file is missing the header, only modify that file to add the header if a change touches that file. Do not make a change to add the header if the file is not being modified. diff --git a/.github/instructions/start-native-execution.instructions.md b/.github/instructions/start-native-execution.instructions.md new file mode 100644 index 00000000000..347e496b3bf --- /dev/null +++ b/.github/instructions/start-native-execution.instructions.md @@ -0,0 +1,149 @@ +--- +applyTo: + - "**/*.ps1" + - "**/*.psm1" +--- + +# Using Start-NativeExecution for Native Command Execution + +## Purpose + +`Start-NativeExecution` is the standard function for executing native commands (external executables) in PowerShell scripts within this repository. It provides consistent error handling and better diagnostics when native commands fail. + +## When to Use + +Use `Start-NativeExecution` whenever you need to: +- Execute external commands (e.g., `git`, `dotnet`, `pkgbuild`, `productbuild`, `fpm`, `rpmbuild`) +- Ensure proper exit code checking +- Get better error messages with caller information +- Handle verbose output on error + +## Basic Usage + +```powershell +Start-NativeExecution { + git clone https://github.com/PowerShell/PowerShell.git +} +``` + +## With Parameters + +Use backticks for line continuation within the script block: + +```powershell +Start-NativeExecution { + pkgbuild --root $pkgRoot ` + --identifier $pkgIdentifier ` + --version $Version ` + --scripts $scriptsDir ` + $outputPath +} +``` + +## Common Parameters + +### -VerboseOutputOnError + +Captures command output and displays it only if the command fails: + +```powershell +Start-NativeExecution -VerboseOutputOnError { + dotnet build --configuration Release +} +``` + +### -IgnoreExitcode + +Allows the command to fail without throwing an exception: + +```powershell +Start-NativeExecution -IgnoreExitcode { + git diff --exit-code # Returns 1 if differences exist +} +``` + +## Availability + +The function is defined in `tools/buildCommon/startNativeExecution.ps1` and is available in: +- `build.psm1` (dot-sourced automatically) +- `tools/packaging/packaging.psm1` (dot-sourced automatically) +- Test modules that include `HelpersCommon.psm1` + +To use in other scripts, dot-source the function: + +```powershell +. "$PSScriptRoot/../buildCommon/startNativeExecution.ps1" +``` + +## Error Handling + +When a native command fails (non-zero exit code), `Start-NativeExecution`: +1. Captures the exit code +2. Identifies the calling location (file and line number) +3. Throws a descriptive error with full context + +Example error message: +``` +Execution of {git clone ...} by /path/to/script.ps1: line 42 failed with exit code 1 +``` + +## Examples from the Codebase + +### Git Operations +```powershell +Start-NativeExecution { + git fetch --tags --quiet upstream +} +``` + +### Build Operations +```powershell +Start-NativeExecution -VerboseOutputOnError { + dotnet publish --configuration Release +} +``` + +### Packaging Operations +```powershell +Start-NativeExecution -VerboseOutputOnError { + pkgbuild --root $pkgRoot --identifier $pkgId --version $version $outputPath +} +``` + +### Permission Changes +```powershell +Start-NativeExecution { + find $staging -type d | xargs chmod 755 + find $staging -type f | xargs chmod 644 +} +``` + +## Anti-Patterns + +**Don't do this:** +```powershell +& somecommand $args +if ($LASTEXITCODE -ne 0) { + throw "Command failed" +} +``` + +**Do this instead:** +```powershell +Start-NativeExecution { + somecommand $args +} +``` + +## Best Practices + +1. **Always use Start-NativeExecution** for native commands to ensure consistent error handling +2. **Use -VerboseOutputOnError** for commands with useful diagnostic output +3. **Use backticks for readability** when commands have multiple arguments +4. **Don't capture output unnecessarily** - let the function handle it +5. **Use -IgnoreExitcode sparingly** - only when non-zero exit codes are expected and acceptable + +## Related Documentation + +- Source: `tools/buildCommon/startNativeExecution.ps1` +- Blog post: https://mnaoumov.wordpress.com/2015/01/11/execution-of-external-commands-in-powershell-done-right/ diff --git a/.github/instructions/start-psbuild-basics.instructions.md b/.github/instructions/start-psbuild-basics.instructions.md new file mode 100644 index 00000000000..18a0026eb2d --- /dev/null +++ b/.github/instructions/start-psbuild-basics.instructions.md @@ -0,0 +1,100 @@ +--- +applyTo: + - "build.psm1" + - "tools/ci.psm1" + - ".github/**/*.yml" + - ".github/**/*.yaml" +--- + +# Start-PSBuild Basics + +## Purpose + +`Start-PSBuild` builds PowerShell from source. It's defined in `build.psm1` and used in CI/CD workflows. + +## Default Usage + +For most scenarios, use with no parameters: + +```powershell +Import-Module ./tools/ci.psm1 +Start-PSBuild +``` + +**Default behavior:** +- Configuration: `Debug` +- PSModuleRestore: Enabled +- Runtime: Auto-detected for platform + +## Common Configurations + +### Debug Build (Default) + +```powershell +Start-PSBuild +``` + +Use for: +- Testing (xUnit, Pester) +- Development +- Debugging + +### Release Build + +```powershell +Start-PSBuild -Configuration 'Release' +``` + +Use for: +- Production packages +- Distribution +- Performance testing + +### Code Coverage Build + +```powershell +Start-PSBuild -Configuration 'CodeCoverage' +``` + +Use for: +- Code coverage analysis +- Test coverage reports + +## Common Parameters + +### -Configuration + +Values: `Debug`, `Release`, `CodeCoverage`, `StaticAnalysis` + +Default: `Debug` + +### -CI + +Restores Pester module for CI environments. + +```powershell +Start-PSBuild -CI +``` + +### -PSModuleRestore + +Now enabled by default. Use `-NoPSModuleRestore` to skip. + +### -ReleaseTag + +Specifies version tag for release builds: + +```powershell +$releaseTag = Get-ReleaseTag +Start-PSBuild -Configuration 'Release' -ReleaseTag $releaseTag +``` + +## Workflow Example + +```yaml +- name: Build PowerShell + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Start-PSBuild +``` diff --git a/.github/instructions/troubleshooting-builds.instructions.md b/.github/instructions/troubleshooting-builds.instructions.md new file mode 100644 index 00000000000..e9b60cb8c80 --- /dev/null +++ b/.github/instructions/troubleshooting-builds.instructions.md @@ -0,0 +1,100 @@ +--- +applyTo: + - "build.psm1" + - "tools/ci.psm1" + - ".github/**/*.yml" + - ".github/**/*.yaml" +--- + +# Troubleshooting Build Issues + +## Git Describe Error + +**Error:** +``` +error MSB3073: The command "git describe --abbrev=60 --long" exited with code 128. +``` + +**Cause:** Insufficient git history (shallow clone) + +**Solution:** Add `fetch-depth: 1000` to checkout step + +```yaml +- name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1000 +``` + +## Version Information Incorrect + +**Symptom:** Build produces wrong version numbers + +**Cause:** Git tags not synchronized + +**Solution:** Run `Sync-PSTags -AddRemoteIfMissing`: + +```yaml +- name: Bootstrap + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Invoke-CIInstall -SkipUser + Sync-PSTags -AddRemoteIfMissing +``` + +## PowerShell Binary Not Built + +**Error:** +``` +Exception: CoreCLR pwsh.exe was not built +``` + +**Causes:** +1. Build failed (check logs) +2. Wrong configuration used +3. Build output location incorrect + +**Solutions:** +1. Check build logs for errors +2. Verify correct configuration for use case +3. Use default parameters: `Start-PSBuild` + +## Module Restore Issues + +**Symptom:** Slow build or module restore failures + +**Causes:** +- Network issues +- Module cache problems +- Package source unavailable + +**Solutions:** +1. Retry the build +2. Check network connectivity +3. Use `-NoPSModuleRestore` if modules not needed +4. Clear package cache if persistent + +## .NET SDK Not Found + +**Symptom:** Build can't find .NET SDK + +**Solution:** Ensure .NET setup step runs first: + +```yaml +- name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + global-json-file: ./global.json +``` + +## Bootstrap Failures + +**Symptom:** Invoke-CIInstall fails + +**Causes:** +- Missing dependencies +- Network issues +- Platform-specific requirements not met + +**Solution:** Check prerequisites for your platform in build system docs diff --git a/.github/policies/IssueManagement.IssueFeedbackForm.yml b/.github/policies/IssueManagement.IssueFeedbackForm.yml deleted file mode 100644 index f42be69ab1e..00000000000 --- a/.github/policies/IssueManagement.IssueFeedbackForm.yml +++ /dev/null @@ -1,22 +0,0 @@ -id: IssueManagement.IssueFeedbackForm -name: GitOps.PullRequestIssueManagement -description: Bot to request the feedback for how we are doing in the repo, replies on closed PRs and issues that are NOT labeled with no activity -owner: -resource: repository -disabled: false -where: -configuration: - resourceManagementConfiguration: - eventResponderTasks: - - if: - - not: - hasLabel: - label: Resolution-No Activity - - isAction: - action: Closed - then: - - addReply: - reply: " 📣 Hey @${issueAuthor}, how did we do? We would love to hear your feedback with the link below! 🗣️ \n\n🔗 https://aka.ms/PSRepoFeedback " - triggerOnOwnActions: true -onFailure: -onSuccess: diff --git a/.github/policies/labelAdded.approvedLowRisk.yml b/.github/policies/labelAdded.approvedLowRisk.yml new file mode 100644 index 00000000000..bdeea5265a0 --- /dev/null +++ b/.github/policies/labelAdded.approvedLowRisk.yml @@ -0,0 +1,48 @@ +id: labelAdded.approvedLowRisk +name: GitOps.PullRequestIssueManagement +description: Remove Approved-LowRisk if applied by an unauthorized user +owner: +resource: repository +disabled: false +where: +configuration: + resourceManagementConfiguration: + eventResponderTasks: + - description: Remove Approved-LowRisk if label was added by someone not authorized + if: + - payloadType: Pull_Request + - isOpen + - labelAdded: + label: Approved-LowRisk + # Unauthorized = NOT admin AND NOT in explicit allowlist + - not: + or: + - activitySenderHasPermission: + permission: Admin + + # Allowlist (enabled) + - isActivitySender: + user: iSazonov + issueAuthor: False + - isActivitySender: + user: daxian-dbw + issueAuthor: False + + # Allowlist (commented out for now) + # - isActivitySender: + # user: TravisEz13 + # issueAuthor: False + # - isActivitySender: + # user: adityapatwardhan + # issueAuthor: False + # - isActivitySender: + # user: jshigetomi + # issueAuthor: False + then: + - removeLabel: + label: Approved-LowRisk + - addReply: + reply: >- + The `Approved-LowRisk` label is restricted to authorized maintainers and was removed. +onFailure: +onSuccess: diff --git a/.github/policies/labelAdded.clBuildPackaging.addBackportConsider.yml b/.github/policies/labelAdded.clBuildPackaging.addBackportConsider.yml new file mode 100644 index 00000000000..78edc18cb1a --- /dev/null +++ b/.github/policies/labelAdded.clBuildPackaging.addBackportConsider.yml @@ -0,0 +1,56 @@ +id: labelAdded.clBuildPackaging.addBackportConsider +name: GitOps.PullRequestIssueManagement +description: Add backport consideration labels when CL-BuildPackaging is added to an open PR targeting master +owner: +resource: repository +disabled: false +where: +configuration: + resourceManagementConfiguration: + eventResponderTasks: + - description: Add BackPort-7.4.x-Consider when CL-BuildPackaging is added to open PR targeting master + if: + - payloadType: Pull_Request + - isOpen + - labelAdded: + label: CL-BuildPackaging + - targetsBranch: + branch: master + - not: + hasLabel: + label: BackPort-7.4.x-Consider + then: + - addLabel: + label: BackPort-7.4.x-Consider + + - description: Add BackPort-7.5.x-Consider when CL-BuildPackaging is added to open PR targeting master + if: + - payloadType: Pull_Request + - isOpen + - labelAdded: + label: CL-BuildPackaging + - targetsBranch: + branch: master + - not: + hasLabel: + label: BackPort-7.5.x-Consider + then: + - addLabel: + label: BackPort-7.5.x-Consider + + - description: Add BackPort-7.6.x-Consider when CL-BuildPackaging is added to open PR targeting master + if: + - payloadType: Pull_Request + - isOpen + - labelAdded: + label: CL-BuildPackaging + - targetsBranch: + branch: master + - not: + hasLabel: + label: BackPort-7.6.x-Consider + then: + - addLabel: + label: BackPort-7.6.x-Consider +onFailure: +onSuccess: diff --git a/.github/prompts/backport-pr-to-release-branch.prompt.md b/.github/prompts/backport-pr-to-release-branch.prompt.md new file mode 100644 index 00000000000..32bff10bd5e --- /dev/null +++ b/.github/prompts/backport-pr-to-release-branch.prompt.md @@ -0,0 +1,567 @@ +--- +description: Guide for backporting changes to PowerShell release branches +--- + +# Backport a Change to a PowerShell Release Branch + +## 1 — Goal + +Create a backport PR that applies changes from a merged PR to a release branch (e.g., `release/v7.4`, `release/v7.5`). The backport must follow the repository's established format and include proper references to the original PR. + +## 2 — Prerequisites for the model + +- You have full repository access +- You can run git commands +- You can read PR information from the repository +- Ask clarifying questions if the target release branch or original PR number is unclear + +## 3 — Required user inputs + +If the user hasn't specified a PR number, help them find one: + +### Finding PRs that need backporting + +1. Ask the user which release version they want to backport to (e.g., `7.4`, `7.5`) +2. Search for PRs with the appropriate label using GitHub CLI: + +```powershell +$Owner = "PowerShell" +$Repo = "PowerShell" +$version = "7.4" # or user-specified version +$considerLabel = "Backport-$version.x-Consider" + +$prsJson = gh pr list --repo "$Owner/$Repo" --label $considerLabel --state merged --json number,title,url,labels,mergedAt --limit 100 2>&1 +$prs = $prsJson | ConvertFrom-Json +# Sort PRs from oldest merged to newest merged +$prs = $prs | Sort-Object mergedAt +``` + +3. Present the list of PRs to the user with: + - PR number + - PR title + - Merged date + - URL + +4. Ask the user: "Which PR would you like to backport?" (provide the PR number) + +### After selecting a PR + +Once the user selects a PR (or if they provided one initially), confirm: +- **Original PR number**: The PR number that was merged to the main branch (e.g., 26193) +- **Target release**: The release number (e.g., `7.4`, `7.5`, `7.5.1`) + +Example: "Backport PR 26193 to release/v7.4" + +## 4 — Implementation steps (must be completed in order) + +### Step 1: Verify the original PR exists and is merged + +1. Fetch the original PR information using the PR number +2. Confirm the PR state is `MERGED` +3. Extract the following information: + - Merge commit SHA + - Original PR title + - Original PR author + - Original CL label (if present, typically starts with `CL-`) + +If the PR is not merged, stop and inform the user. + +4. Check if backport already exists or has been attempted: + ```powershell + gh pr list --repo PowerShell/PowerShell --search "in:title [release/v7.4] " --state all + ``` + + If a backport PR already exists, inform the user and ask if they want to continue. + +5. Check backport labels to understand status: + - `Backport-7.4.x-Migrated`: Indicates previous backport attempt (may have failed or had issues) + - `Backport-7.4.x-Done`: Already backported successfully + - `Backport-7.4.x-Approved`: Ready for backporting + - `Backport-7.4.x-Consider`: Under consideration for backporting + + If status is "Done", inform the user that backport may already be complete. + +### Step 2: Create the backport branch + +1. Identify the correct remote to fetch from: + ```bash + git remote -v + ``` + + Look for the remote that points to `https://github.com/PowerShell/PowerShell` (typically named `upstream` or `origin`). Use this remote name in subsequent commands. + +2. Ensure you have the latest changes from the target release branch: + ```bash + git fetch + ``` + + Example: `git fetch upstream release/v7.4` + +3. Create a new branch from the target release branch: + ```bash + git checkout -b backport- / + ``` + + Example: `git checkout -b backport-26193 upstream/release/v7.4` + +### Step 3: Cherry-pick the merge commit + +1. Cherry-pick the merge commit from the original PR: + ```bash + git cherry-pick + ``` + +2. If conflicts occur: + - Inform the user about the conflicts + - List the conflicting files + - Fetch the original PR diff to understand the changes: + ```bash + gh pr diff --repo PowerShell/PowerShell | Out-File pr-diff.txt + ``` + - Review the diff to understand what the PR changed + - Figure out why there is a conflict and resolve it + - Create a summary of the conflict resolution: + * Which files had conflicts + * Nature of each conflict (parameter changes, code removal, etc.) + * How you resolved it + * Whether any manual adjustments were needed beyond accepting one side + - Ask the user to review your conflict resolution summary before continuing + - After conflicts are resolved, continue with: + ```bash + git add + git cherry-pick --continue + ``` + +### Step 4: Push the backport branch + +Push to your fork (typically the remote that you have write access to): + +```bash +git push backport- +``` + +Example: `git push origin backport-26193` + +Note: If you're pushing to the official PowerShell repository and have permissions, you may push to `upstream` or the appropriate remote. + +### Step 5: Create the backport PR + +Create a new PR with the following format: + +**Title:** +``` +[] +``` + +Example: `[release/v7.4] GitHub Workflow cleanup` + +**Body:** +``` +Backport of # to + + + +Triggered by @ on behalf of @ + +Original CL Label: + +/cc @PowerShell/powershell-maintainers + +## Impact + +Choose either tooling or Customer impact. +### Tooling Impact + +- [ ] Required tooling change +- [ ] Optional tooling change (include reasoning) + +### Customer Impact + +- [ ] Customer reported +- [ ] Found internally + +[Select one or both of the boxes. Describe how this issue impacts customers, citing the expected and actual behaviors and scope of the issue. If customer-reported, provide the issue number.] + +## Regression + +- [ ] Yes +- [ ] No + +[If yes, specify when the regression was introduced. Provide the PR or commit if known.] + +## Testing + +[How was the fix verified? How was the issue missed previously? What tests were added?] + +## Risk + +- [ ] High +- [ ] Medium +- [ ] Low + +[High/Medium/Low. Justify the indication by mentioning how risks were measured and addressed.] +``` + +**Base branch:** `` (e.g., `release/v7.4`) + +**Head branch:** `backport-` (e.g., `backport-26193`) + +#### Guidelines for Filling Out the PR Body + +**For Impact Section**: +- If the original PR changed build/tooling/packaging, select "Tooling Impact" +- If it fixes a user-facing bug or changes user-visible behavior, select "Customer Impact" +- Copy relevant context from the original PR description +- Be specific about what changed and why + +**For Regression Section**: +- Mark "Yes" only if the original PR fixed a regression +- Include when the regression was introduced if known + +**For Testing Section**: +- Reference the original PR's testing approach +- Note any additional backport-specific testing needed +- Mention if manual testing was done to verify the backport + +**For Risk Assessment**: +- **High**: Changes core functionality, packaging, build systems, or security-related code +- **Medium**: Changes non-critical features, adds new functionality, or modifies existing behavior +- **Low**: Documentation, test-only changes, minor refactoring, or fixes with narrow scope +- Justify your assessment based on the scope of changes and potential impact +- **For CI/CD changes**: When backporting CI/CD infrastructure changes (workflows, build scripts, packaging), note in your justification that not taking these changes may create technical debt and make it difficult to apply future CI/CD changes that build on top of them. This doesn't change the risk level itself, but provides important context for why the change should be taken despite potentially higher risk + +**If there were merge conflicts**: +Add a note in the PR description after the Risk section describing what conflicts occurred and how they were resolved. + +### Step 6: Add the CL label to the backport PR + +After creating the backport PR, add the same changelog label (CL-*) from the original PR to the backport PR: + +```bash +gh pr edit --repo PowerShell/PowerShell --add-label "" +``` + +Example: `gh pr edit 26389 --repo PowerShell/PowerShell --add-label "CL-BuildPackaging"` + +This ensures the backport is properly categorized in the changelog for the release branch. + +### Step 7: Update the original PR's backport labels + +After successfully creating the backport PR, update the original PR to reflect that it has been backported: + +```bash +gh pr edit --repo PowerShell/PowerShell --add-label "Backport-.x-Migrated" --remove-label "Backport-.x-Consider" +``` + +Example: `gh pr edit 26193 --repo PowerShell/PowerShell --add-label "Backport-7.5.x-Migrated" --remove-label "Backport-7.5.x-Consider"` + +Notes: +- If the original PR had `Backport-.x-Approved` instead of `Consider`, remove that label +- This step helps track which PRs have been successfully backported +- The `Migrated` label indicates the backport PR has been created (not necessarily merged) +- The `Done` label should only be added once the backport PR is merged + +### Step 8: Clean up temporary files + +After successful PR creation and labeling, clean up any temporary files created during the process: + +```powershell +Remove-Item pr*.diff -ErrorAction SilentlyContinue +``` + +## 5 — Definition of Done (self-check list) + +- [ ] Original PR is verified as merged +- [ ] Checked for existing backport PRs +- [ ] Reviewed backport labels to understand status +- [ ] Backport branch created from correct release branch +- [ ] Merge commit cherry-picked successfully (or conflicts resolved) +- [ ] If conflicts occurred, provided resolution summary to user +- [ ] Branch pushed to origin +- [ ] PR created with correct title format: `[] ` +- [ ] CL label added to backport PR (matching original PR's CL label) +- [ ] Original PR labels updated (added Migrated, removed Consider/Approved) +- [ ] Temporary files cleaned up (pr*.diff) +- [ ] PR body includes: + - [ ] Backport reference: `Backport of (PR-number) to ` + - [ ] Auto-generated comment with original PR number + - [ ] Triggered by and original author attribution + - [ ] Original CL label (if available) + - [ ] CC to PowerShell maintainers + - [ ] Impact section filled out + - [ ] Regression section filled out + - [ ] Testing section filled out + - [ ] Risk section filled out +- [ ] Base branch set to target release branch +- [ ] No unrelated changes included + +## 6 — Branch naming convention + +**Format:** `backport/release//pr/` + +Examples: +- `backport/release/v7.5/pr/26193` +- `backport/release/v7.4.1/pr/26334` + +Note: Automated bot uses format `backport/release/v/-`, but manual backports should use the format `backport/release//pr/` as shown above. + +## 7 — Example backport PR + +Reference PR 26334 as the canonical example of a correct backport: + +**Original PR**: PR 26193 "GitHub Workflow cleanup" + +**Backport PR**: PR 26334 "[release/v7.4] GitHub Workflow cleanup" +- **Title**: `[release/v7.4] GitHub Workflow cleanup` +- **Body**: Started with backport reference to original PR and release branch +- **Branch**: `backport/release/v7.4/26193-4aff02475` (bot-created) +- **Base**: `release/v7.4` +- **Includes**: Auto-generated metadata, impact assessment, regression info, testing details, and risk level + +## 8 — Backport label system (for context) + +Backport labels follow pattern: `Backport-.x-` + +**Triage states:** +- `Consider` - Under review for backporting +- `Approved` - Approved and ready to be backported +- `Done` - Backport completed + +**Examples:** `Backport-7.4.x-Approved`, `Backport-7.5.x-Consider`, `Backport-7.3.x-Done` + +Note: The PowerShell repository has an automated bot (pwshBot) that creates backport PRs automatically when a merged PR has a backport approval label. Manual backports follow the same format. + +## Manual Backport Using PowerShell Tools + +For situations where automated backports fail or manual intervention is needed, use the `Invoke-PRBackport` function from `tools/releaseTools.psm1`. + +### Prerequisites + +1. **GitHub CLI**: Install from https://cli.github.com/ + - Required version: 2.17 or later + - Authenticate with `gh auth login` + +2. **Upstream Remote**: Configure a Git remote named `upstream` pointing to `PowerShell/PowerShell`: + ```powershell + git remote add upstream https://github.com/PowerShell/PowerShell.git + ``` + +### Using Invoke-PRBackport + +```powershell +# Import the release tools module +Import-Module ./tools/releaseTools.psm1 + +# Backport a single PR +Invoke-PRBackport -PrNumber 26193 -Target release/v7.4.1 + +# Backport with custom branch postfix +Invoke-PRBackport -PrNumber 26193 -Target release/v7.4.1 -BranchPostFix "retry" + +# Overwrite existing local branch if it exists +Invoke-PRBackport -PrNumber 26193 -Target release/v7.4.1 -Overwrite +``` + +### Parameters + +- **PrNumber** (Required): The PR number to backport +- **Target** (Required): Target release branch (must match pattern `release/v\d+\.\d+(\.\d+)?`) +- **Overwrite**: Switch to overwrite local branch if it already exists +- **BranchPostFix**: Add a postfix to the branch name (e.g., for retry attempts) +- **UpstreamRemote**: Name of the upstream remote (default: `upstream`) + +### How It Works + +1. Verifies the PR is merged +2. Fetches the target release branch from upstream +3. Creates a new branch: `backport-[-]` +4. Cherry-picks the merge commit +5. If conflicts occur, prompts you to resolve them +6. Creates the backport PR using GitHub CLI + +## Handling Merge Conflicts + +When cherry-picking fails due to conflicts: + +1. The script will pause and prompt you to fix conflicts +2. Resolve conflicts in your editor: + ```powershell + # Check which files have conflicts + git status + + # Edit files to resolve conflicts + # After resolving, stage the changes + git add + + # Continue the cherry-pick + git cherry-pick --continue + ``` +3. Type 'Yes' when prompted to continue the script +4. The script will create the PR + +### Understanding Conflict Patterns + +When resolving conflicts during backports, follow this approach: + +1. **Analyze the diff first**: Before resolving conflicts, fetch and review the original PR's diff to understand what changed: + ```powershell + gh pr diff --repo PowerShell/PowerShell | Out-File pr-diff.txt + ``` + +2. **Identify conflict types**: + - **Parameter additions**: New parameters added to functions (e.g., ValidateSet values) + - **Code removal**: Features removed in main but still exist in release branch + - **Code additions**: New code blocks that don't exist in release branch + - **Refactoring conflicts**: Code structure changes between branches + +3. **Resolution priorities**: + - Preserve the intent of the backported change + - Keep release branch-specific code that doesn't conflict with the fix + - When in doubt, favor the incoming change from the backport + - Document significant manual changes in the PR description + +4. **Verification**: + - After resolving conflicts, verify the file compiles/runs + - Check that the resolved code matches the original PR's intent + - Look for orphaned code that references removed functions + +5. **Create a conflict resolution summary**: + - List which files had conflicts + - Briefly explain the nature of each conflict + - Describe how you resolved it + - Ask user to review the resolution before continuing + +### Context-Aware Conflict Resolution + +**Key Principle**: The release branch may have different code than main. Your goal is to apply the *change* from the PR, not necessarily make the code identical to main. + +**Common Scenarios**: +1. **Function parameters differ**: If the release branch has fewer parameters than main, and the backport adds functionality unrelated to new parameters, keep the release branch parameters unless the new parameters are part of the fix +2. **Dependencies removed in main**: If main removed a dependency but the release branch still has it, and the backport is unrelated to that dependency, keep the release branch code +3. **New features in main**: If main has new features not in the release, focus on backporting only the specific fix, not the new features + +## Bulk Backporting Approved PRs + +To backport all PRs labeled as approved for a specific version: + +```powershell +Import-Module ./tools/releaseTools.psm1 + +# Backport all approved PRs for version 7.2.12 +Invoke-PRBackportApproved -Version 7.2.12 +``` + +This function: +1. Queries all merged PRs with the `Backport-.x-Approved` label +2. Attempts to backport each PR in order of merge date +3. Creates individual backport PRs for each + +## Viewing Backport Reports + +Get a list of PRs that need backporting: + +```powershell +Import-Module ./tools/releaseTools.psm1 + +# List all approved backports for 7.4 +Get-PRBackportReport -Version 7.4 -TriageState Approved + +# Open all approved backports in browser +Get-PRBackportReport -Version 7.4 -TriageState Approved -Web + +# Check which backports are done +Get-PRBackportReport -Version 7.4 -TriageState Done +``` + +## Branch Naming Conventions + +### Automated Bot Branches +Format: `backport/release/v/-` + +Example: `backport/release/v7.4/26193-4aff02475` + +### Manual Backport Branches +Format: `backport-[-]` + +Examples: +- `backport-26193` +- `backport-26193-retry` + +## PR Title and Description Format + +### Title +Format: `[release/v] ` + +Example: `[release/v7.4] GitHub Workflow cleanup` + +### Description +The backport PR description includes: +- Reference to original PR number +- Target release branch +- Auto-generated comment with original PR metadata +- Maintainer information +- Original CL label +- CC to PowerShell maintainers team + +Example description structure: +```text +Backport of (original-pr-number) to release/v + + + +Triggered by @ on behalf of @ + +Original CL Label: + +/cc @PowerShell/powershell-maintainers +``` + +## Best Practices + +1. **Verify PR is merged**: Only backport merged PRs +2. **Test backports**: Ensure backported changes work in the target release context +3. **Check for conflicts early**: Large PRs are more likely to have conflicts +4. **Use appropriate labels**: Apply correct version and triage state labels +5. **Document special cases**: If manual changes were needed, note them in the PR description +6. **Follow up on CI failures**: Backports should pass all CI checks before merging + +## Troubleshooting + +### "PR is not merged" Error +**Cause**: Attempting to backport a PR that hasn't been merged yet +**Solution**: Wait for the PR to be merged to the main branch first + +### "Please create an upstream remote" Error +**Cause**: No upstream remote configured +**Solution**: +```powershell +git remote add upstream https://github.com/PowerShell/PowerShell.git +git fetch upstream +``` + +### "GitHub CLI is not installed" Error +**Cause**: gh CLI not found in PATH +**Solution**: Install from https://cli.github.com/ and restart terminal + +### Cherry-pick Conflicts +**Cause**: Changes conflict with the target branch +**Solution**: Manually resolve conflicts, stage files, and continue cherry-pick + +### "Commit does not exist" Error +**Cause**: Local Git doesn't have the commit +**Solution**: +```powershell +git fetch upstream +``` + +## Related Resources + +- **Release Process**: See `docs/maintainers/releasing.md` +- **Release Tools**: See `tools/releaseTools.psm1` +- **Issue Management**: See `docs/maintainers/issue-management.md` diff --git a/.github/skills/analyze-pester-failures/SKILL.md b/.github/skills/analyze-pester-failures/SKILL.md new file mode 100644 index 00000000000..ec1b0fe82ec --- /dev/null +++ b/.github/skills/analyze-pester-failures/SKILL.md @@ -0,0 +1,524 @@ +--- +name: analyze-pester-failures +description: Troubleshooting guide for analyzing and investigating Pester test failures in PowerShell CI jobs. Help agents understand why tests are failing, interpret test output, navigate test result artifacts, and provide actionable recommendations for fixing test issues. +--- + +# Analyze Pester Test Failures + +Investigate and troubleshoot Pester test failures in GitHub Actions workflows. Understand what tests are failing, why they're failing, and provide recommendations for test fixes. + +| Skill | When to Use | +|-------|-----------| +| analyze-pester-failures | When investigating why Pester tests are failing in a CI job. Use when a test job shows failures and you need to understand what test failed, why it failed, what the error message means, and what might need to be fixed. Also use when asked: "why did this test fail?", "what's the test error?", "test is broken", "test failure analysis", "debug test failure", or given test failure logs and stack traces. | + +## When to Use This Skill + +Use this skill when you need to: + +- Understand why a specific Pester test is failing +- Interpret test failure messages and error output +- Analyze test result data from CI workflow runs (XML, logs, stack traces) +- Identify the root cause of test failures (test logic, assertion failure, exception, timeout, skip/ignore reason) +- Provide recommendations for fixing failing tests +- Compare expected vs. actual test behavior +- Debug test environment issues (missing dependencies, configuration problems) +- Understand test skip/ignored/inconclusive status reasons + +**Do not use this skill for:** +- General PowerShell debugging unrelated to tests +- Test infrastructure/CI setup issues (except as they affect test failure interpretation) +- Performance analysis or benchmarking (that's a different investigation) + +## Quick Start + +### ⚠️ CRITICAL: The Workflow Must Be Followed IN ORDER + +This skill describes a **sequential 6-step analysis workflow**. Skipping steps or jumping around leads to **incomplete analysis and incorrect conclusions**. + +**The Problem**: It's easy to skip to Step 4 or 5 without doing Steps 1-2, resulting in missing data and bad conclusions. + +**The Solution**: Use the automated analysis script to enforce the workflow: + +```powershell +# Automatically runs Steps 1-6 in order, preventing skipping +./.github/skills/analyze-pester-failures/scripts/analyze-pr-test-failures.ps1 -PR + +# Example: +./.github/skills/analyze-pester-failures/scripts/analyze-pr-test-failures.ps1 -PR 26800 +``` + +This script: +1. ✓ Fetches PR status automatically +2. ✓ Downloads artifacts (can't skip, depends on Step 1) +3. ✓ Extracts failures (can't skip, depends on Step 2) +4. ✓ Analyzes error messages +5. ✓ Documents context +6. ✓ Generates recommendations + +**Only use the manual commands below if you fully understand the workflow.** + +### Manual Workflow (for reference) + +```powershell +# Step 1: Identify the failing job +gh pr view --json 'statusCheckRollup' | ConvertFrom-Json | Where-Object { $_.conclusion -eq 'FAILURE' } + +# Step 2: Download artifacts (extract RUN_ID from Step 1) +gh run download --dir ./artifacts +gh run view --log > test-logs.txt + +# Step 3-6: Extract, analyze, and interpret +# (See Analysis Workflow section below) +``` + +## Common Test Failure Analysis Approaches + +### 1. **Interpreting Assertion Failures** +The most common test failure is when an assertion doesn't match expectations. + +**Example:** +``` +Expected $true but got $false at /path/to/test.ps1:42 +Assertion failed: Should -Be "expected" but was "actual" +``` + +**How to analyze:** +- Read the assertion message: what was expected vs. what was actual? +- Check the test logic: is the expectation correct? +- Look for mock/stub issues: are dependencies configured correctly? +- Check parameter values: what inputs were passed to the function under test? + +### 2. **Exception Failures** +Tests fail when PowerShell throws an exception instead of successful completion. + +**Example:** +``` +Command: Write-Host $null +Error: Cannot bind argument to parameter 'Object' because it is null. +``` + +**How to analyze:** +- Read the exception message: what operation failed? +- Check the stack trace: where in the test or tested code did it throw? +- Verify preconditions: does the test setup provide required values/mocks? +- Look for environmental issues: missing modules, permissions, file system state? + +### 3. **Timeout Failures** +A test takes longer than the allowed timeout to complete. + +**Example:** +``` +Test 'Should complete in reasonable time' timed out after 30 seconds +``` + +**How to analyze:** +- Is the timeout appropriate for this test type? (network tests need more time) +- Is there an infinite loop in the test or tested code? +- Are there resource contention issues on the CI runner? +- Does the test hang waiting for something (file lock, network, process)? + +### 4. **Skip/Ignored Reason Analysis** +Tests marked as skipped or ignored provide clues about test environment. + +**Example:** +``` +Test marked [Skip("Only runs on Windows")] - running on Linux +Test marked [Ignore("Known issue #12345")] +``` + +**How to analyze:** +- Read the skip/ignore reason: is it still valid? +- Check if environment has changed: platform, module versions, etc. +- Verify issue status: is the known issue still open? Has it been fixed? +- Determine if skip should be removed or if test needs environment changes + +### 5. **Flaky/Intermittent Failures** +Tests that sometimes pass, sometimes fail indicate race conditions or environment sensitivity. + +**Example:** +- Test passes locally but fails on CI +- Test passes first run of suite, fails on second run +- Test passes on Windows but fails on Linux + +**How to analyze:** +- Look for timeout races: is timing involved in the test? +- Check for test isolation issues: does one test affect another? +- Verify environment differences: CI vs. local paths, permissions, versions +- Look for external dependencies: network calls, file I/O, process interactions + +## Key Artifacts and Locations + +| Item | Purpose | Location | +|------|---------|----------| +| Test result XML | Pester output with test cases, failures, errors | Workflow artifacts: `junit-pester-*.xml` | +| Job logs | Full job output including test execution and errors | GitHub Actions run logs or `gh run download` | +| Stack traces | Error location information from failed assertions | Within job logs and XML failure messages | +| Test files | The actual Pester test code (`.ps1` files) | `test/` directory in repository | + +## Analysis Workflow + +### ⚠️ Important: These Steps MUST Be Followed In Order + +Each step depends on the previous one. Skipping or re-ordering steps causes incomplete analysis: + +- **Step 1** (identify jobs) → You get the RUN_ID needed for Step 2 +- **Step 2** (download) → You get the artifacts needed for Step 3 +- **Step 3** (extract) → You discover what failures exist for Step 4 +- **Step 4** (read messages) → You understand the errors to analyze in Step 5 +- **Step 5** (context) → You gather information to make recommendations in Step 6 +- **Step 6** (interpret) → You use all above to recommend fixes + +**Real Problem We Had**: +- ❌ Jumped to Step 3 without Step 1-2 +- ❌ Used random test data from context instead of downloading PR artifacts +- ❌ Skipped Steps 5-6 entirely +- ❌ Made recommendations without full context + +**Result**: Wrong analysis and recommendations that didn't actually fix the problem. + +### Recommended: Use the Automated Script + +```powershell +./.github/skills/analyze-pester-failures/scripts/analyze-pr-test-failures.ps1 -PR +``` + +This enforces the workflow and prevents skipping. + +### Step 2: Get Test Results + +Fetch the test result artifacts and job logs: + +```powershell +# Download artifacts including test XML results +gh run download --dir ./artifacts + +# Get job logs +gh run view --log > test-logs.txt + +# Inspect test XML +$xml = [xml](Get-Content ./artifacts/junit-pester-*.xml) +$xml.'test-results' | Select-Object total, failures, errors, ignored, inconclusive +``` + +### Step 3: Extract Specific Failures + +Find the failing test cases in the XML: + +```powershell +# Get all failed test cases +$xml = [xml](Get-Content ./artifacts/junit-pester-*.xml) +$failures = $xml.SelectNodes('.//test-case[@result = "Failure"]') + +# For each failure, display key info +$failures | ForEach-Object { + [PSCustomObject]@{ + Name = $_.name + Description = $_.description + Message = $_.failure.message + StackTrace = $_.failure.'stack-trace' + } +} +``` + +### Step 4: Read the Error Message + +The error message tells you what went wrong: + +**Assertion failures:** +``` +Expected $true but got $false +Expected "value1" but got "value2" +Expression should have failed with exception, but didn't +``` + +**Exceptions:** +``` +Cannot find a parameter with name 'Name' +Property 'Property' does not exist on 'Object' +Cannot bind argument to parameter because it is null +``` + +**Timeouts:** +``` +Test timed out after 30 seconds +Test is taking too long to complete +``` + +### Step 5: Understand the Context + +Look at the test file to understand what was being tested: + +```powershell +# Find the test file mentioned in the stack trace +# Example: /path/to/test/Feature.Tests.ps1:42 + +# Read the test code around that line +code : + +# Understand: +# - What assertion is on that line? +# - What is the test trying to verify? +# - What are the setup/mock/before conditions? +# - Are there recent changes to the function being tested? +``` + +### Step 6: Interpret the Failure + +Determine the root cause category: + +**Test issue (needs code fix):** +- Assertion logic is wrong +- Test expectations don't match actual behavior +- Test setup is incomplete +- Mock/stub configuration missing + +**Environmental issue (needs environment change):** +- Test assumes a specific file or registry entry exists +- Test requires Windows/Linux specifically +- Test requires specific PowerShell version +- Test requires specific module version +- Timing-sensitive test affected by CI load + +**Data issue (needs input data change):** +- Test data no longer valid +- External API changed format +- Configuration file has changed structure + +**Flakiness (needs test hardening):** +- Race condition in test +- Timing assumptions too tight +- Resource contention on CI runner +- Non-deterministic behavior in tested code + +## Common Test Failure Patterns + +| Pattern | What It Means | Example | Next Step | +|---------|---------------|---------|-----------| +| `Expected $true but got $false` | Assertion on boolean result failed | Test expects function returns true, but it returns false | Check function logic for bug or test logic for wrong expectation | +| `Cannot find path` | File or directory doesn't exist | Test tries to read config file that's not present | Verify file path, check test setup, ensure CI environment has file | +| `Cannot bind argument to parameter 'X'` | Required parameter value is null or wrong type | Function called with $null where object expected | Check test mock setup, verify parameter types | +| `Test timed out after X seconds` | Test exceeded time limit | Network call or loop takes too long | Increase timeout for slow test, find infinite loop, mock network calls | +| `Expression should have failed but didn't` | Exception wasn't thrown when expected | Test expects error but function succeeds | Check if function behavior changed, update test expectation | +| `Could not find parameter 'X'` | Function doesn't have parameter | Test calls function with parameter that doesn't exist | Check PowerShell version, verify function signature, update test | +| `This platform is not supported` | Test skipped on current OS | Windows-only test running on Linux | Add platform check, update test environment, or mark as platform-specific | +| `Test marked [Ignore]` | Test explicitly disabled | Test has `[Ignore("reason")]` attribute | Check if reason still valid, remove if issue fixed | + +## Interpreting Test Results + +### Test Result Counts + +Pester test outcomes are categorized as: + +| Count | Meaning | Notes | +|-------|---------|-------| +| `total` | Total number of test cases executed | Should match: passed + failed + errors + skipped + ignored | +| `failures` | Test assertions that failed | `Expected X but got Y` type failures | +| `errors` | Tests that threw exceptions | Unhandled PowerShell exceptions during test | +| `skipped` | Tests explicitly skipped (marked with `-Skip`) | Test code recognizes condition and skips | +| `ignored` | Tests marked as ignored (marked with `-Ignore`) | Test disabled intentionally, usually notes reason | +| `inconclusive` | Tests with unclear result | Rare; usually means test framework issue | +| `passed` | Tests with passing assertions | `total - failures - errors - skipped - ignored` | + +### Stack Trace Interpretation + +A stack trace shows where the failure occurred: + +``` +at /home/runner/work/PowerShell/test/Feature.Tests.ps1:42 + +Means: +- File: /home/runner/work/PowerShell/test/Feature.Tests.ps1 +- Line: 42 +- Look at that line to see which assertion failed +``` + +### Understanding Skipped Tests + +When XML shows `result="Ignored"` or `result="Skipped"`: + +```xml + + Only runs on Windows + +``` + +The reason explains why test didn't run. Not a failure, but important for understanding test coverage. + +## Providing Test Failure Analysis + +### Investigation Questions + +After gathering test output, ask yourself: + +1. **Is the test code correct?** + - Does the test assertion match the expected behavior? + - Are test expectations still valid? + - Has the function being tested changed? + +2. **Is the test setup correct?** + - Are mocks/stubs configured properly? + - Does the test environment have required files/configuration? + - Are preconditions (database, files, services) met? + +3. **Is this a code bug or test issue?** + - Does the tested function have a logic error? + - Or does the test have incorrect expectations? + +4. **Is this environment-specific?** + - Only fails on Windows/Linux? + - Only fails on CI but passes locally? + - Timing-dependent or resource-dependent? + +5. **Is this a known/expected failure?** + - Is there already an issue tracking this failure? + - Is the test marked as flaky or expected to fail? + - Does the skip/ignore reason still apply? + +### Recommendation Framework + +Based on your analysis: + +| Finding | Recommendation | +|---------|-----------------| +| Test logic is wrong | "Test assertion on line X is incorrect. Test expects Y but function correctly returns Z. Update test expectation." | +| Tested code has bug | "Function at file.ps1#L42 has logic error. When X happens, returns Y instead of Z. Fix the condition." | +| Missing test setup | "Test setup incomplete. Mock for dependency Y is not configured. Add `Mock Get-Y -MockWith { ... }`" | +| Environment issue | "Test is Windows-specific but running on Linux. Either add platform check or skip on non-Windows." | +| Flaky test | "Test is timing-sensitive (sleep 1 second). Increase timeout or use better synchronization." | +| Test should be skipped | "Test is marked Ignored for good reason. Keep it disabled until issue #12345 is fixed." | + +### Tone and Structure + +Provide analysis as: + +1. **Summary** (1 sentence): What test is failing and general category +2. **Failure Details** (2-3 sentences): What the test output says +3. **Root Cause** (1-2 sentences): Why it's failing (test bug vs. code bug vs. environment) +4. **Recommendation** (actionable): What should be done to fix it +5. **Context** (optional): Link to related code, issues, or recent changes + +## Examples + +### Example 1: Assertion Failure Due to Code Bug + +**Test Output:** +``` +Expected 5 but got 3 at /path/to/Test.ps1:42 +``` + +**Investigation:** +1. Look at line 42: `$result | Should -Be 5` +2. Check the test: It expects function to return 5 items +3. Check the function: It returns `$items | Where-Object {$_.Status -eq "Active"}` but the filter is wrong +4. Root cause: Function has logic error, not test error + +**Recommendation:** +``` +Test failure is due to a code bug: + +The test Set-Configuration should return 5 items but returns 3. + +Looking at the tested function at [module.ps1#L42](module.ps1#L42): + $activeItems = $items | Where-Object {$_.Status -eq "Active"} + +The issue is the filter condition. It's currently filtering by "Active" status, +but should include "Pending" status as well. + +Fix: Change line 42 to: + $activeItems = $items | Where-Object {$_.Status -ne "Disabled"} + +Then re-run the test to verify it now returns 5 items as expected. +``` + +### Example 2: Test Setup Issue + +**Test Output:** +``` +Cannot find path '/expected/config.json' because it does not exist at /path/to/Test.ps1:15 +``` + +**Investigation:** +1. Line 15 tries to read a config file +2. The test setup doesn't create this file +3. Works locally but fails on CI because CI doesn't have the same file + +**Recommendation:** +``` +Test setup is incomplete: + +The test Initialize-Config fails because it expects /expected/config.json but the test doesn't create this file. + +The test needs to ensure the config file exists. Currently line 12-14 doesn't set up the file: + + # Before: + # (no setup of config file) + + # After: + @{ setting1 = "value1"; setting2 = "value2" } | ConvertTo-Json | + Out-File $testConfigPath + +Alternatively, the test function should accept a parameter for the config path and use a temporary file: + param([string]$ConfigPath = (New-TemporaryFile)) + +Re-run the test to verify the config file is properly available. +``` + +### Example 3: Platform-Specific Test Failure + +**Test Output:** +``` +Test 'should read Windows Registry' failed on Linux runner +Cannot find path 'HKEY_LOCAL_MACHINE:\...' +``` + +**Investigation:** +1. Test assumes Windows Registry exists (Windows-only) +2. Running on Linux runner doesn't have Registry +3. Test should skip on non-Windows platforms + +**Recommendation:** +``` +Test is platform-specific but running on wrong platform: + +The test "should read Windows Registry" assumes Windows Registry exists but is running on Linux. + +Add a platform check to skip this test on non-Windows systems: + + It "should read Windows Registry" -Skip:$(-not $IsWindows) { + # test code here + } + +Or group Windows-only tests in a separate Describe block with platform check: + + Describe "Windows Registry Tests" -Skip:$(-not $IsWindows) { + # all Windows-specific tests here + } + +This allows the test to be skipped on Linux/Mac while still running on Windows CI. +``` + +## References + +- [Pester Testing Framework](https://pester.dev/) — Official documentation, best practices for test writing +- [Test Files](../../../test/) — PowerShell test suite in repository +- [GitHub Actions Documentation](https://docs.github.com/en/actions) — Understanding workflow runs and logs +- [PowerShell Documentation](https://learn.microsoft.com/en-us/powershell/) — Language reference for understanding test code + +## Tips + +1. **Read the error message first:** The error message is usually the most direct clue to the problem +2. **Check test vs. code blame:** Is the test wrong or is the code wrong? Look at both sides +3. **Verify test isolation:** Does one test failure affect others? Check for shared state or test ordering dependencies +4. **Test locally first:** Try running the failing test locally to reproduce and understand it better +5. **Check for environmental assumptions:** Windows-specific paths, module versions, file locations may differ on CI +6. **Look for skip/ignore patterns:** If a test is consistently ignored, check if the reason is still valid +7. **Compare passing vs. failing:** If test passes locally but fails on CI, the difference is usually environment-related +8. **Check recent changes:** Did a recent PR change the tested code or test itself? +9. **Understand Pester output format:** Different Pester versions, different `-ErrorAction`, `-WarningAction` produce different test results +10. **Don't assume CI is wrong:** Failures on CI often reveal real issues that local testing missed (network, file permissions, parallelization, etc.) + +## Additional Links + +- [PowerShell Repository](https://github.com/PowerShell/PowerShell) +- [GitHub Actions Documentation](https://docs.github.com/en/actions) +- [Pester Testing Framework](https://github.com/Pester/Pester) diff --git a/.github/skills/analyze-pester-failures/references/stack-trace-parsing.md b/.github/skills/analyze-pester-failures/references/stack-trace-parsing.md new file mode 100644 index 00000000000..707f45560a9 --- /dev/null +++ b/.github/skills/analyze-pester-failures/references/stack-trace-parsing.md @@ -0,0 +1,163 @@ +# Understanding Pester Test Failures + +This reference explains how to interpret Pester test output and understand failure messages. + +## Supported Formats + +### Pester 4 Format +``` +at line: 123 in C:\path\to\file.ps1 +``` + +**Regex Pattern:** +```powershell +if ($StackTraceString -match 'at line:\s*(\d+)\s+in\s+(.+?)(?:\r|\n|$)') { + $result.Line = $matches[1] + $result.File = $matches[2].Trim() + return $result +} +``` + +### Pester 5 Format (Common) +``` +at 1 | Should -Be 2, C:\path\to\file.ps1:123 +at 1 | Should -Be 2, /home/runner/work/PowerShell/PowerShell/test/file.ps1:123 +``` + +**Regex Pattern:** +```powershell +if ($StackTraceString -match ',\s*((?:[A-Za-z]:)?[\/\\].+?\.ps[m]?1):(\d+)') { + $result.File = $matches[1].Trim() + $result.Line = $matches[2] + return $result +} +``` + +### Alternative Format +``` +at C:\path\to\file.ps1:123 +at /path/to/file.ps1:123 +``` + +**Regex Pattern:** +```powershell +if ($StackTraceString -match 'at\s+((?:[A-Za-z]:)?[\/\\][^,]+?\.ps[m]?1):(\d+)(?:\r|\n|$)') { + $result.File = $matches[1].Trim() + $result.Line = $matches[2] + return $result +} +``` + +## Troubleshooting Parsing Failures + +### Issue: Line Number Extracted But File Path Is Null + +**Cause:** Stack trace matches line-with-path pattern but file extraction doesn't work + +**Solution:** +1. Check if file path exists as expected in filesystem +2. Verify regex doesn't have too-greedy bounds (check use of `.+?` vs `.+`) +3. Test regex against actual stack trace string: + ```powershell + $trace = "at line: 42 in C:\path\to\test.ps1" + if ($trace -match 'at line:\s*(\d+)\s+in\s+(.+?)(?:\r|\n|$)') { + Write-Host "File: $($matches[2])" # Should be "C:\path\to\test.ps1" + } + ``` + +### Issue: Special Characters in File Path Break Regex + +**Cause:** Characters like parens `()`, brackets `[]`, pipes `|` have special meaning in regex + +**Solution:** +1. Escape special chars in regex: `[Regex]::Escape($path)` +2. Use character class `[\/\\]` instead of alternation for path separators +3. Test with files containing problematic names: + ```powershell + $traces = @( + "at line: 1 in C:\path\(with)\parens\test.ps1", + "at /home/user/[brackets]/test.ps1:5", + "at C:\path\with spaces\test.ps1:10" + ) + # Test each against all patterns + ``` + +### Issue: Regex Matches But Extracts Wrong Values + +**Symptom:** $matches[1] is file instead of line, or vice versa + +**Debug Steps:** +1. Print all captured groups: `$matches.Values | Format-Table -AutoSize` +2. Verify group order in regex matches expectations +3. Test with sample Pester output: + ```powershell + $sampleTrace = @" + at 1 | Should -Be 2, /home/runner/work/PowerShell/test/file.ps1:42 + "@ + + if ($sampleTrace -match ',\s*((?:[A-Za-z]:)?[\/\\].+?\.ps[m]?1):(\d+)') { + Write-Host "Match 1: $($matches[1])" # Should be file path + Write-Host "Match 2: $($matches[2])" # Should be line number + } + ``` + +## Testing the Parser + +Use this PowerShell script to validate `Get-PesterFailureFileInfo`: + +```powershell +# Import the function +. ./build.psm1 + +$testCases = @( + @{ + Input = "at line: 42 in C:\path\to\test.ps1" + Expected = @{ File = "C:\path\to\test.ps1"; Line = "42" } + }, + @{ + Input = "at /home/runner/work/test.ps1:123" + Expected = @{ File = "/home/runner/work/test.ps1"; Line = "123" } + }, + @{ + Input = "at 1 | Should -Be 2, /path/to/file.ps1:99" + Expected = @{ File = "/path/to/file.ps1"; Line = "99" } + } +) + +foreach ($test in $testCases) { + $result = Get-PesterFailureFileInfo -StackTraceString $test.Input + + $fileMatch = $result.File -eq $test.Expected.File + $lineMatch = $result.Line -eq $test.Expected.Line + $status = if ($fileMatch -and $lineMatch) { "✓ PASS" } else { "✗ FAIL" } + + Write-Host "$status : $($test.Input)" + if (-not $fileMatch) { Write-Host " Expected file: $($test.Expected.File), got: $($result.File)" } + if (-not $lineMatch) { Write-Host " Expected line: $($test.Expected.Line), got: $($result.Line)" } +} +``` + +## Adding Support for New Formats + +When Pester changes its output format: + +1. **Capture sample output** from failing tests +2. **Identify the pattern** (e.g., "file path always after comma followed by colon") +3. **Write regex** to match pattern without over-matching +4. **Add to `Get-PesterFailureFileInfo`** before existing patterns (order matters for fallback) +5. **Test with samples** containing special characters, long paths, and edge cases + +Example: Adding a new format at the top of the function: + +```powershell +# Try pattern: "at , :" (Pester 5.1 hypothetical) +if ($StackTraceString -match 'at .+?, ((?:[A-Za-z]:)?[\/\\].+?\.ps[m]?1):(\d+)') { + $result.File = $matches[1].Trim() + $result.Line = $matches[2] + return $result +} + +# Try existing patterns... +``` + +Place new patterns **first** so they take precedence over fallback patterns. diff --git a/.github/skills/analyze-pester-failures/scripts/analyze-pr-test-failures.ps1 b/.github/skills/analyze-pester-failures/scripts/analyze-pr-test-failures.ps1 new file mode 100644 index 00000000000..12486596071 --- /dev/null +++ b/.github/skills/analyze-pester-failures/scripts/analyze-pr-test-failures.ps1 @@ -0,0 +1,456 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +<# +.SYNOPSIS + Automated Pester test failure analysis workflow for GitHub PRs. + +.DESCRIPTION + This script automates the complete analysis workflow defined in the analyze-pester-failures + skill. It performs all steps in order: + 1. Identify failing test jobs in the PR + 2. Download test artifacts and logs + 3. Extract specific test failures + 4. Parse error messages + 5. Search logs for error markers and generate recommendations + + By automating the workflow, this ensures analysis steps are followed in order + and nothing is skipped. + +.PARAMETER PR + The GitHub PR number to analyze (e.g., 26800) + +.PARAMETER Owner + Repository owner (default: PowerShell) + +.PARAMETER Repo + Repository name (default: PowerShell) + +.PARAMETER OutputDir + Directory to store analysis results (default: ./pester-analysis-PR) + +.PARAMETER Interactive + Prompt for recommendations after analysis (default: non-interactive) + +.PARAMETER ForceDownload + Force re-download of artifacts and logs, even if they already exist + +.EXAMPLE + .\.github\skills\analyze-pester-failures\scripts\analyze-pr-test-failures.ps1 -PR 26800 + Analyzes PR #26800 and saves results to ./pester-analysis-PR26800 + +.EXAMPLE + .\.github\skills\analyze-pester-failures\scripts\analyze-pr-test-failures.ps1 -PR 26800 -Interactive + Interactive mode: shows failures and prompts for next steps + +.EXAMPLE + .\.github\skills\analyze-pester-failures\scripts\analyze-pr-test-failures.ps1 -PR 26800 -ForceDownload + Re-download all logs and artifacts, skipping the cache + +.NOTES + Requires: GitHub CLI (gh) configured and authenticated + This script enforces the workflow defined in .github/skills/analyze-pester-failures/SKILL.md +#> + +param( + [Parameter(Mandatory)] + [int]$PR, + + [string]$Owner = 'PowerShell', + [string]$Repo = 'PowerShell', + [string]$OutputDir, + [switch]$Interactive, + [switch]$ForceDownload +) + +$ErrorActionPreference = 'Stop' + +if (-not $OutputDir) { + $OutputDir = "./pester-analysis-PR$PR" +} + +# Colors for output +$colors = @{ + Step = [ConsoleColor]::Cyan + Success = [ConsoleColor]::Green + Warning = [ConsoleColor]::Yellow + Error = [ConsoleColor]::Red + Info = [ConsoleColor]::Gray +} + +function Write-Step { + param([string]$text, [int]$number) + Write-Host "`n[$number/6] $text" -ForegroundColor $colors.Step -BackgroundColor Black +} + +function Write-Result { + param([string]$text, [ValidateSet('Success','Warning','Error','Info')]$type = 'Info') + Write-Host $text -ForegroundColor $colors[$type] +} + +Write-Host "`n=== Pester Test Failure Analysis ===" -ForegroundColor $colors.Step +Write-Host "PR: $Owner/$Repo#$PR" -ForegroundColor $colors.Info +Write-Host "Output Directory: $OutputDir" -ForegroundColor $colors.Info + +# Ensure output directory exists +if (-not (Test-Path $OutputDir)) { + New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null +} + +# STEP 1: Identify the Failing Test Job +Write-Step "Identify failing test jobs" 1 + +Write-Result "Fetching PR status checks..." Info +$prResponse = gh pr view $PR --repo "$Owner/$Repo" --json 'statusCheckRollup' | ConvertFrom-Json +$allChecks = $prResponse.statusCheckRollup + +$failedJobs = $allChecks | Where-Object { $_.conclusion -eq 'FAILURE' } + +if (-not $failedJobs) { + Write-Result "✓ No failed jobs found" Success + Write-Host " Total checks: $($allChecks.Count)" + $allChecks | Where-Object { $_ } | ForEach-Object { + Write-Host " - $($_.name): $($_.conclusion)" -ForegroundColor $colors.Info + } + exit 0 +} + +Write-Result "✓ Found $($failedJobs.Count) failing job(s)" Warning + +$failedJobs | Where-Object { $_.conclusion -eq 'FAILURE' } | ForEach-Object { + Write-Host " ✗ $($_.name) - $($_.conclusion)" -ForegroundColor $colors.Error + if ($_.detailsUrl) { + Write-Host " URL: $($_.detailsUrl)" -ForegroundColor $colors.Info + } +} + +if ($Interactive) { + Write-Host "`nPress Enter to continue to Step 2..." + Read-Host | Out-Null +} + +# STEP 2: Get Test Results +Write-Step "Download test artifacts and logs" 2 + +# Extract unique run IDs from failing jobs +$uniqueRuns = @() +foreach ($failedJob in $failedJobs) { + if ($failedJob.detailsUrl -match 'runs/(\d+)') { + $runId = $matches[1] + if ($runId -notin $uniqueRuns) { + $uniqueRuns += $runId + } + } +} + +if ($uniqueRuns.Count -eq 0) { + Write-Result "✗ Could not extract run IDs from failing jobs" Error + exit 1 +} + +Write-Result "Found $($uniqueRuns.Count) run(s): $($uniqueRuns -join ', ')" Info + +$artifactDir = Join-Path $OutputDir artifacts + +# Check if artifacts already exist +$existingArtifacts = Get-ChildItem $artifactDir -Recurse -File -ErrorAction SilentlyContinue + +if ($existingArtifacts -and -not $ForceDownload) { + Write-Result "✓ Artifacts already downloaded" Success + $existingArtifacts | ForEach-Object { + Write-Host " - $($_.FullName)" -ForegroundColor $colors.Info + } +} else { + Write-Result "Downloading artifacts from run $($uniqueRuns[0])..." Info + gh run download $uniqueRuns[0] --dir $artifactDir --repo "$Owner/$Repo" 2>&1 | Out-Null + + if (Test-Path $artifactDir) { + Write-Result "✓ Artifacts downloaded" Success + Get-ChildItem $artifactDir -Recurse -File | ForEach-Object { + Write-Host " - $($_.FullName)" -ForegroundColor $colors.Info + } + } else { + Write-Result "✗ Failed to download artifacts" Error + exit 1 + } +} + +# Download individual job logs for failing jobs +Write-Result "Downloading individual job logs..." Info + +$logsDir = Join-Path $OutputDir "logs" +if (-not (Test-Path $logsDir)) { + New-Item -ItemType Directory -Path $logsDir -Force | Out-Null +} + +# Check if logs already exist +$existingLogs = Get-ChildItem $logsDir -Filter "*.txt" -ErrorAction SilentlyContinue + +if ($existingLogs -and -not $ForceDownload) { + Write-Result "✓ Job logs already downloaded" Success + $existingLogs | ForEach-Object { + Write-Host " - $($_.Name)" -ForegroundColor $colors.Info + } +} else { + # Process each run and get its jobs + $failedJobIds = @() + foreach ($runId in $uniqueRuns) { + $runJobs = gh run view $runId --repo "$Owner/$Repo" --json jobs | ConvertFrom-Json + + foreach ($failedJob in $failedJobs) { + # Check if this failed job belongs to this run + if ($failedJob.detailsUrl -match "runs/$runId/") { + $jobMatch = $runJobs.jobs | Where-Object { $_.name -eq $failedJob.name } | Select-Object -First 1 + if ($jobMatch) { + $failedJobIds += @{ + name = $failedJob.name + id = $jobMatch.databaseId + runId = $runId + } + } + } + } + } + + # Download logs for all failed jobs + foreach ($jobInfo in $failedJobIds) { + $logFile = Join-Path $logsDir ("log-{0}.txt" -f ($jobInfo.name -replace '[^a-zA-Z0-9-]', '_')) + Write-Result " Downloading: $($jobInfo.name) (Run $($jobInfo.runId))" Info + gh run view $jobInfo.runId --log --job $jobInfo.id --repo "$Owner/$Repo" > $logFile 2>&1 + } + + Write-Result "✓ Job logs downloaded" Success + Get-ChildItem $logsDir -Filter "*.txt" | ForEach-Object { + Write-Host " - $($_.Name)" -ForegroundColor $colors.Info + } +} + +if ($Interactive) { + Write-Host "`nPress Enter to continue to Step 3..." + Read-Host | Out-Null +} + +# STEP 3: Extract Specific Failures +Write-Step "Extract test failures from XML" 3 + +$xmlFiles = Get-ChildItem $artifactDir -Filter "*.xml" -Recurse +if (-not $xmlFiles) { + Write-Result "✗ No test result XML files found" Error + exit 1 +} + +Write-Result "✓ Found $($xmlFiles.Count) test result file(s)" Success + +$allFailures = @() + +foreach ($xmlFile in $xmlFiles) { + Write-Result "`nParsing: $($xmlFile.Name)" Info + + try { + [xml]$xml = Get-Content $xmlFile + $testResults = $xml.'test-results' + + Write-Host " Total: $($testResults.total)" -ForegroundColor $colors.Info + Write-Host " Passed: $($testResults.passed)" -ForegroundColor $colors.Success + if ($testResults.failures -gt 0) { + Write-Host " Failed: $($testResults.failures)" -ForegroundColor $colors.Error + } + if ($testResults.errors -gt 0) { + Write-Host " Errors: $($testResults.errors)" -ForegroundColor $colors.Error + } + if ($testResults.skipped -gt 0) { + Write-Host " Skipped: $($testResults.skipped)" -ForegroundColor $colors.Warning + } + if ($testResults.ignored -gt 0) { + Write-Host " Ignored: $($testResults.ignored)" -ForegroundColor $colors.Warning + } + + # Extract failures + $failures = $xml.SelectNodes('.//test-case[@result = "Failure"]') + + foreach ($failure in $failures) { + $allFailures += @{ + Name = $failure.name + File = $xmlFile.Name + Message = $failure.failure.message + StackTrace = $failure.failure.'stack-trace' + } + } + } catch { + Write-Result "✗ Error parsing XML: $_" Error + } +} + +Write-Result "`n✓ Extracted $($allFailures.Count) failures total" Success + +# Save failures to JSON for later analysis +$allFailures | ConvertTo-Json -Depth 10 | Out-File (Join-Path $OutputDir "failures.json") + +if ($Interactive) { + Write-Host "`nPress Enter to continue to Step 4..." + Read-Host | Out-Null +} + +# STEP 4: Read Error Messages +Write-Step "Analyze error messages" 4 + +$failuresByType = @{} + +foreach ($failure in $allFailures) { + $message = $failure.Message -split "`n" | Select-Object -First 1 + + # Categorize failure + $type = 'Other' + if ($message -match 'Expected .* but got') { $type = 'Assertion' } + elseif ($message -match 'Cannot (find|bind)') { $type = 'Exception' } + elseif ($message -match 'timed out') { $type = 'Timeout' } + + if (-not $failuresByType[$type]) { + $failuresByType[$type] = @() + } + $failuresByType[$type] += $failure +} + +Write-Result "Failure breakdown:" Info +$failuresByType.GetEnumerator() | ForEach-Object { + Write-Host " $($_.Key): $($_.Value.Count)" -ForegroundColor $colors.Warning +} + +Write-Result "`nTop failure messages:" Info +$allFailures | Group-Object Message | Sort-Object Count -Descending | Select-Object -First 3 | ForEach-Object { + Write-Host " [$($_.Count)x] $($_.Name -split "`n" | Select-Object -First 1)" -ForegroundColor $colors.Info +} + +# Save analysis +$analysis = @{ + FailuresByType = @{} + TopMessages = @() +} + +$failuresByType.GetEnumerator() | ForEach-Object { + $analysis.FailuresByType[$_.Key] = $_.Value.Count +} + +$allFailures | Group-Object Message | Sort-Object Count -Descending | Select-Object -First 5 | ForEach-Object { + $analysis.TopMessages += @{ + Count = $_.Count + Message = ($_.Name -split "`n" | Select-Object -First 1) + } +} + +$analysis | ConvertTo-Json | Out-File (Join-Path $OutputDir "analysis.json") + +if ($Interactive) { + Write-Host "`nPress Enter to continue to Step 5..." + Read-Host | Out-Null +} + +# STEP 5: Search Logs for Error Markers +Write-Step "Search logs for error markers" 5 + +$logsDir = Join-Path $OutputDir "logs" +if (-not (Test-Path $logsDir)) { + Write-Result "⚠ Logs directory not found" Warning +} else { + $logFiles = Get-ChildItem $logsDir -Filter "*.txt" -ErrorAction SilentlyContinue + if (-not $logFiles) { + Write-Result "⚠ No log files found in logs directory" Warning + } else { + Write-Result "Searching $($logFiles.Count) job log(s) for error markers ([-])" Info + Write-Result "Format: [JobName] [LineNumber] Content" Info + Write-Host "" + + $allErrorLines = @() + + foreach ($logFile in $logFiles) { + $jobName = $logFile.BaseName -replace '^log-', '' + $logLines = @(Get-Content $logFile) + + for ($i = 0; $i -lt $logLines.Count; $i++) { + $line = $logLines[$i] + if ($line -match '\s\[-\]\s') { + $allErrorLines += @{ + JobName = $jobName + LineNumber = $i + 1 + Content = $line + } + } + } + } + + if ($allErrorLines.Count -gt 0) { + Write-Result "✓ Found $($allErrorLines.Count) error marker line(s)" Warning + + $allErrorLines | ForEach-Object { + Write-Host " [$($_.JobName)] [$($_.LineNumber)] $($_.Content)" -ForegroundColor $colors.Error + } + + # Save to file + $allErrorLines | ConvertTo-Json | Out-File (Join-Path $OutputDir "error-markers.json") + Write-Result "✓ Error markers saved to error-markers.json" Success + } else { + Write-Result "✓ No error markers found in logs" Success + } + } +} + +if ($Interactive) { + Write-Host "`nPress Enter to continue to Step 6..." + Read-Host | Out-Null +} + +# STEP 6: Generate Recommendations +Write-Step "Generate recommendations" 6 + +$recommendations = @() + +# Analyze patterns +if ($failuresByType['Assertion']) { + $recommendations += "Multiple assertion failures detected. These indicate test expectations don't match actual behavior." +} + +if ($failuresByType['Exception']) { + $recommendations += "Exception errors found. Check test setup and prerequisites - may indicate missing files, modules, or permissions." +} + +if ($failuresByType['Timeout']) { + $recommendations += "Timeout failures suggest slow or hanging operations. Consider network issues or resource constraints on CI." +} + +# Check for patterns in failure messages +$failureMessages = $allFailures.Message -join "`n" +if ($failureMessages -match 'PackageManagement') { + $recommendations += "PackageManagement module issues detected. Verify module availability and help repository access." +} + +if ($failureMessages -match 'Update-Help') { + $recommendations += "Update-Help failures detected. Check network connectivity to help repository and help installation paths." +} + +Write-Result "`n📋 Recommendations:" Info +if ($recommendations) { + $recommendations | ForEach-Object { Write-Host " • $_" -ForegroundColor $colors.Info } +} else { + Write-Host " • Review failures in detail" -ForegroundColor $colors.Info + Write-Host " • Check if test changes are needed" -ForegroundColor $colors.Info + Write-Host " • Consider environment-specific issues" -ForegroundColor $colors.Info +} + +$recommendations | Out-File (Join-Path $OutputDir "recommendations.txt") + +# Summary +Write-Host "`n=== Analysis Complete ===" -ForegroundColor $colors.Step +Write-Host "Results saved to: $OutputDir" -ForegroundColor $colors.Info +Write-Host " - failures.json (detailed failure data)" -ForegroundColor $colors.Info +Write-Host " - analysis.json (summary analysis)" -ForegroundColor $colors.Info +Write-Host " - recommendations.txt (suggested fixes)" -ForegroundColor $colors.Info +Write-Host " - error-markers.json (error markers from logs)" -ForegroundColor $colors.Info +Write-Host " - logs/ (individual job log files)" -ForegroundColor $colors.Info +Write-Host " - artifacts/ (downloaded test artifacts)" -ForegroundColor $colors.Info + +Write-Host "`nNext steps:" -ForegroundColor $colors.Step +Write-Host "1. Review recommendations.txt for analysis" -ForegroundColor $colors.Info +Write-Host "2. Examine failures.json for detailed error messages" -ForegroundColor $colors.Info +Write-Host "3. Check error-markers.json for specific test failures in logs" -ForegroundColor $colors.Info +Write-Host "4. Review individual job logs in logs/ directory for contextual details" -ForegroundColor $colors.Info +Write-Host "`n" diff --git a/.github/workflows/AssignPrs.yml b/.github/workflows/AssignPrs.yml deleted file mode 100644 index a01c0bb0950..00000000000 --- a/.github/workflows/AssignPrs.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Auto Assign PR Maintainer -on: - issues: - types: [opened, edited] -permissions: - contents: read - -jobs: - run: - if: github.repository_owner == 'PowerShell' - runs-on: ubuntu-latest - permissions: - issues: write - pull-requests: write - steps: - - uses: wow-actions/auto-assign@67fafa03df61d7e5f201734a2fa60d1ab111880d # v3.0.2 - if: github.event.issue.pull_request - with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # using the `org/team_slug` or `/team_slug` syntax to add git team as reviewers - assignees: | - TravisEz13 - daxian-dbw - adityapatwardhan - iSazonov - SeeminglyScience - skipDraft: true - skipKeywords: wip, draft - addReviewers: false - numberOfAssignees: 1 diff --git a/.github/workflows/analyze-reusable.yml b/.github/workflows/analyze-reusable.yml new file mode 100644 index 00000000000..3ef564eba90 --- /dev/null +++ b/.github/workflows/analyze-reusable.yml @@ -0,0 +1,77 @@ +name: CodeQL Analysis (Reusable) + +on: + workflow_call: + inputs: + runner_os: + description: 'Runner OS for CodeQL analysis' + type: string + required: false + default: ubuntu-latest + +permissions: + actions: read # for github/codeql-action/init to get workflow details + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/analyze to upload SARIF results + +env: + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + DOTNET_NOLOGO: 1 + POWERSHELL_TELEMETRY_OPTOUT: 1 + __SuppressAnsiEscapeSequences: 1 + nugetMultiFeedWarnLevel: none + +jobs: + analyze: + name: Analyze + runs-on: ${{ inputs.runner_os }} + + strategy: + fail-fast: false + matrix: + # Override automatic language detection by changing the below list + # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] + language: ['csharp'] + # Learn more... + # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection + + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: '0' + + - uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 + with: + global-json-file: ./global.json + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v3.29.5 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + - run: | + Import-Module .\tools\ci.psm1 + Show-Environment + name: Capture Environment + shell: pwsh + + - run: | + Import-Module .\tools\ci.psm1 + Invoke-CIInstall -SkipUser + name: Bootstrap + shell: pwsh + + - run: | + Import-Module .\tools\ci.psm1 + Invoke-CIBuild -Configuration 'StaticAnalysis' + name: Build + shell: pwsh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v3.29.5 diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml new file mode 100644 index 00000000000..d78e745a4a9 --- /dev/null +++ b/.github/workflows/copilot-setup-steps.yml @@ -0,0 +1,64 @@ +name: "Copilot Setup Steps" + +# Allow testing of the setup steps from your repository's "Actions" tab. +on: + workflow_dispatch: + + pull_request: + branches: + - master + paths: + - ".github/workflows/copilot-setup-steps.yml" + +permissions: + contents: read + +jobs: + # The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot. + # See https://docs.github.com/en/copilot/customizing-copilot/customizing-the-development-environment-for-copilot-coding-agent + copilot-setup-steps: + runs-on: ubuntu-latest + + permissions: + contents: read + + # You can define any steps you want, and they will run before the agent starts. + # If you do not check out your code, Copilot will do this for you. + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + + - name: Bootstrap + if: success() + run: |- + $title = 'Import Build.psm1' + Write-Host "::group::$title" + Import-Module ./build.psm1 -Verbose -ErrorAction Stop + Write-LogGroupEnd -Title $title + + $title = 'Switch to public feed' + Write-LogGroupStart -Title $title + Switch-PSNugetConfig -Source Public + Write-LogGroupEnd -Title $title + + $title = 'Bootstrap' + Write-LogGroupStart -Title $title + Start-PSBootstrap -Scenario DotNet + Write-LogGroupEnd -Title $title + + $title = 'Install .NET Tools' + Write-LogGroupStart -Title $title + Start-PSBootstrap -Scenario Tools + Write-LogGroupEnd -Title $title + + $title = 'Sync Tags' + Write-LogGroupStart -Title $title + Sync-PSTags -AddRemoteIfMissing + Write-LogGroupEnd -Title $title + + $title = 'Setup .NET environment variables' + Write-LogGroupStart -Title $title + Find-DotNet -SetDotnetRoot + Write-LogGroupEnd -Title $title + shell: pwsh diff --git a/.github/workflows/createReminders.yml b/.github/workflows/createReminders.yml deleted file mode 100644 index bab5342fe0b..00000000000 --- a/.github/workflows/createReminders.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: 'Create reminder' - -on: - issue_comment: - types: [created, edited] - -permissions: - contents: read - -jobs: - reminder: - if: github.repository_owner == 'PowerShell' - - permissions: - issues: write # for agrc/create-reminder-action to set reminders on issues - pull-requests: write # for agrc/create-reminder-action to set reminders on PRs - runs-on: ubuntu-latest - - steps: - - name: check for reminder - uses: agrc/create-reminder-action@30624e347adbc7ff2dd287ad0632499552e048e8 # v1.1.22 diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index b85021ab000..84f0b03fa32 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -17,6 +17,6 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout Repository' - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: 'Dependency Review' - uses: actions/dependency-review-action@da24556b548a50705dd671f47852072ea4c105d9 # v4.7.1 + uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index befb416aeaf..27ceac59bbd 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -18,11 +18,11 @@ jobs: steps: - name: Check out the repository - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Verify PR has label starting with 'cl-' id: verify-labels - uses: actions/github-script@v7 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const labels = context.payload.pull_request.labels.map(label => label.name.toLowerCase()); diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index 513db9cc487..77186125a9c 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -12,6 +12,8 @@ on: - github-mirror paths: - "**" + - "*" + - ".globalconfig" - "!.github/ISSUE_TEMPLATE/**" - "!.dependabot/config.yml" - "!.pipelines/**" @@ -21,15 +23,16 @@ on: - master - release/** - github-mirror + - "*-feature" # Path filters for PRs need to go into the changes job concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }} cancel-in-progress: ${{ contains(github.ref, 'merge')}} env: DOTNET_CLI_TELEMETRY_OPTOUT: 1 - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 + DOTNET_NOLOGO: 1 FORCE_FEATURE: 'False' FORCE_PACKAGE: 'False' NUGET_KEY: none @@ -50,9 +53,11 @@ jobs: # Set job outputs to values from filter step outputs: source: ${{ steps.filter.outputs.source }} + buildModuleChanged: ${{ steps.filter.outputs.buildModuleChanged }} + packagingChanged: ${{ steps.filter.outputs.packagingChanged }} steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false @@ -62,14 +67,28 @@ jobs: with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + merge_conflict_check: + name: Check for Merge Conflict Markers + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' && (startsWith(github.repository_owner, 'azure') || github.repository_owner == 'PowerShell') + permissions: + pull-requests: read + contents: read + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Check for merge conflict markers + uses: "./.github/actions/infrastructure/merge-conflict-checker" + ci_build: name: Build PowerShell runs-on: ubuntu-latest needs: changes - if: ${{ needs.changes.outputs.source == 'true' }} + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1000 @@ -80,11 +99,11 @@ jobs: needs: - ci_build - changes - if: ${{ needs.changes.outputs.source == 'true' }} + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} runs-on: ubuntu-latest steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1000 - name: Linux Unelevated CI @@ -92,16 +111,17 @@ jobs: with: purpose: UnelevatedPesterTests tagSet: CI + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} linux_test_elevated_ci: name: Linux Elevated CI needs: - ci_build - changes - if: ${{ needs.changes.outputs.source == 'true' }} + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} runs-on: ubuntu-latest steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1000 - name: Linux Elevated CI @@ -109,16 +129,17 @@ jobs: with: purpose: ElevatedPesterTests tagSet: CI + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} linux_test_unelevated_others: name: Linux Unelevated Others needs: - ci_build - changes - if: ${{ needs.changes.outputs.source == 'true' }} + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} runs-on: ubuntu-latest steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1000 - name: Linux Unelevated Others @@ -126,16 +147,17 @@ jobs: with: purpose: UnelevatedPesterTests tagSet: Others + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} linux_test_elevated_others: name: Linux Elevated Others needs: - ci_build - changes - if: ${{ needs.changes.outputs.source == 'true' }} + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} runs-on: ubuntu-latest steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1000 - name: Linux Elevated Others @@ -143,107 +165,98 @@ jobs: with: purpose: ElevatedPesterTests tagSet: Others - verify_xunit: - name: Verify xUnit test results + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + xunit_tests: + name: xUnit Tests needs: - - ci_build - changes - if: ${{ needs.changes.outputs.source == 'true' }} + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} + uses: ./.github/workflows/xunit-tests.yml + with: + runner_os: ubuntu-latest + test_results_artifact_name: testResults-xunit + + infrastructure_tests: + name: Infrastructure Tests runs-on: ubuntu-latest steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - fetch-depth: 1000 - - name: Verify xUnit test results - uses: "./.github/actions/test/verify_xunit" + fetch-depth: 1 - analyze: - permissions: - actions: read # for github/codeql-action/init to get workflow details - contents: read # for actions/checkout to fetch code - security-events: write # for github/codeql-action/analyze to upload SARIF results - name: Analyze - runs-on: ubuntu-latest - needs: changes - if: ${{ needs.changes.outputs.source == 'true' }} + - name: Install Pester + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Install-CIPester - strategy: - fail-fast: false - matrix: - # Override automatic language detection by changing the below list - # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] - language: ['csharp'] - # Learn more... - # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection + - name: Run Infrastructure Tests + shell: pwsh + run: | + $testResultsFolder = Join-Path $PWD "testResults" + New-Item -ItemType Directory -Path $testResultsFolder -Force | Out-Null - steps: - - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - fetch-depth: '0' - - - uses: actions/setup-dotnet@v4 - with: - global-json-file: ./global.json - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - - - run: | - Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose - name: Capture Environment - shell: pwsh - - - run: | - Import-Module .\tools\ci.psm1 - Invoke-CIInstall -SkipUser - name: Bootstrap - shell: pwsh - - - run: | - Import-Module .\tools\ci.psm1 - Invoke-CIBuild - name: Build - shell: pwsh - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5 + $config = New-PesterConfiguration + $config.Run.Path = './test/infrastructure/' + $config.Run.PassThru = $true + $config.TestResult.Enabled = $true + $config.TestResult.OutputFormat = 'NUnitXml' + $config.TestResult.OutputPath = "$testResultsFolder/InfrastructureTests.xml" + $config.Output.Verbosity = 'Detailed' + + $result = Invoke-Pester -Configuration $config + + if ($result.FailedCount -gt 0 -or $result.Result -eq 'Failed') { + throw "Infrastructure tests failed" + } + + - name: Publish Test Results + uses: "./.github/actions/test/process-pester-results" + if: always() + with: + name: "InfrastructureTests" + testResultsFolder: "${{ github.workspace }}/testResults" + + ## Temporarily disable the CodeQL analysis on Linux as it doesn't work for .NET SDK 10-rc.2. + # analyze: + # name: CodeQL Analysis + # needs: changes + # if: ${{ needs.changes.outputs.source == 'true' }} + # uses: ./.github/workflows/analyze-reusable.yml + # permissions: + # actions: read + # contents: read + # security-events: write + # with: + # runner_os: ubuntu-latest ready_to_merge: name: Linux ready to merge needs: - - verify_xunit + - xunit_tests - linux_test_elevated_ci - linux_test_elevated_others - linux_test_unelevated_ci - linux_test_unelevated_others - - analyze + - linux_packaging + - merge_conflict_check + - infrastructure_tests + # - analyze if: always() - uses: PowerShell/compliance/.github/workflows/ready-to-merge.yml@v1.0.0 + uses: PowerShell/compliance/.github/workflows/ready-to-merge.yml@c8b3ad5819ad7078f3e375519b4f8c6232d1cbdf # v1.0.0 with: needs_context: ${{ toJson(needs) }} - # TODO: Enable this when we have a Linux packaging workflow - - # ERROR: While executing gem ... (Gem::FilePermissionError) - # You don't have write permissions for the /var/lib/gems/2.7.0 directory. - # WARNING: Installation of gem dotenv 2.8.1 failed! Must resolve manually. - - # linux_packaging: - # name: Attempt Linux Packaging - # needs: ci_build - # runs-on: ubuntu-20.04 - # steps: - # - name: checkout - # uses: actions/checkout@v4 - # with: - # fetch-depth: 1000 - # - name: Verify xUnit test results - # uses: "./.github/actions/test/linux-packaging" + linux_packaging: + name: Linux Packaging + needs: + - changes + if: ${{ needs.changes.outputs.packagingChanged == 'true' }} + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + - name: Linux Packaging + uses: "./.github/actions/test/linux-packaging" diff --git a/.github/workflows/macos-ci.yml b/.github/workflows/macos-ci.yml index 18a684de2d9..55d852bb68a 100644 --- a/.github/workflows/macos-ci.yml +++ b/.github/workflows/macos-ci.yml @@ -10,6 +10,8 @@ on: - github-mirror paths: - "**" + - "*" + - ".globalconfig" - "!.github/ISSUE_TEMPLATE/**" - "!.dependabot/config.yml" - "!.pipelines/**" @@ -19,15 +21,16 @@ on: - master - release/** - github-mirror + - "*-feature" # Path filters for PRs need to go into the changes job concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }} cancel-in-progress: ${{ contains(github.ref, 'merge')}} env: DOTNET_CLI_TELEMETRY_OPTOUT: 1 - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 + DOTNET_NOLOGO: 1 FORCE_FEATURE: 'False' FORCE_PACKAGE: 'False' HOMEBREW_NO_ANALYTICS: 1 @@ -50,9 +53,11 @@ jobs: # Set job outputs to values from filter step outputs: source: ${{ steps.filter.outputs.source }} + buildModuleChanged: ${{ steps.filter.outputs.buildModuleChanged }} + packagingChanged: ${{ steps.filter.outputs.packagingChanged }} steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Change Detection id: filter @@ -62,12 +67,12 @@ jobs: ci_build: name: Build PowerShell - runs-on: macos-latest + runs-on: macos-15-large needs: changes - if: ${{ needs.changes.outputs.source == 'true' }} + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1000 - name: Build @@ -77,11 +82,11 @@ jobs: needs: - ci_build - changes - if: ${{ needs.changes.outputs.source == 'true' }} - runs-on: macos-latest + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} + runs-on: macos-15-large steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1000 - name: macOS Unelevated CI @@ -89,16 +94,17 @@ jobs: with: purpose: UnelevatedPesterTests tagSet: CI + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} macos_test_elevated_ci: name: macOS Elevated CI needs: - ci_build - changes - if: ${{ needs.changes.outputs.source == 'true' }} - runs-on: macos-latest + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} + runs-on: macos-15-large steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1000 - name: macOS Elevated CI @@ -106,16 +112,17 @@ jobs: with: purpose: ElevatedPesterTests tagSet: CI + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} macos_test_unelevated_others: name: macOS Unelevated Others needs: - ci_build - changes - if: ${{ needs.changes.outputs.source == 'true' }} - runs-on: macos-latest + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} + runs-on: macos-15-large steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1000 - name: macOS Unelevated Others @@ -123,16 +130,17 @@ jobs: with: purpose: UnelevatedPesterTests tagSet: Others + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} macos_test_elevated_others: name: macOS Elevated Others needs: - ci_build - changes - if: ${{ needs.changes.outputs.source == 'true' }} - runs-on: macos-latest + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} + runs-on: macos-15-large steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1000 - name: macOS Elevated Others @@ -140,46 +148,101 @@ jobs: with: purpose: ElevatedPesterTests tagSet: Others - verify_xunit: - name: Verify xUnit test results + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + xunit_tests: + name: xUnit Tests needs: - - ci_build - changes - if: ${{ needs.changes.outputs.source == 'true' }} - runs-on: ubuntu-latest - steps: - - name: checkout - uses: actions/checkout@v4 - with: - fetch-depth: 1000 - - name: Verify xUnit test results - uses: "./.github/actions/test/verify_xunit" + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} + uses: ./.github/workflows/xunit-tests.yml + with: + runner_os: macos-15-large + test_results_artifact_name: testResults-xunit PackageMac-macos_packaging: - name: macOS packaging (bootstrap only) + name: macOS packaging and testing needs: - changes - if: ${{ needs.changes.outputs.source == 'true' }} + if: ${{ needs.changes.outputs.packagingChanged == 'true' }} runs-on: - - macos-latest + - macos-15-large steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + - uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 + with: + global-json-file: ./global.json - name: Bootstrap packaging - if: success() || failure() + if: success() run: |- import-module ./build.psm1 start-psbootstrap -Scenario package shell: pwsh + - name: Build PowerShell and Create macOS package + if: success() + run: |- + import-module ./build.psm1 + import-module ./tools/ci.psm1 + import-module ./tools/packaging/packaging.psm1 + Switch-PSNugetConfig -Source Public + Sync-PSTags -AddRemoteIfMissing + $releaseTag = Get-ReleaseTag + Start-PSBuild -Configuration Release -PSModuleRestore -ReleaseTag $releaseTag + $macOSRuntime = if ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture -eq 'Arm64') { 'osx-arm64' } else { 'osx-x64' } + Start-PSPackage -Type osxpkg -ReleaseTag $releaseTag -MacOSRuntime $macOSRuntime -SkipReleaseChecks + shell: pwsh + + - name: Install Pester + if: success() + run: |- + Import-Module ./tools/ci.psm1 + Install-CIPester + shell: pwsh + + - name: Test package contents + if: success() + run: |- + $env:PACKAGE_FOLDER = Get-Location + $testResultsPath = Join-Path $env:RUNNER_WORKSPACE "testResults" + if (-not (Test-Path $testResultsPath)) { + New-Item -ItemType Directory -Path $testResultsPath -Force | Out-Null + } + Import-Module Pester + $pesterConfig = New-PesterConfiguration + $pesterConfig.Run.Path = './test/packaging/macos/package-validation.tests.ps1' + $pesterConfig.Run.PassThru = $true + $pesterConfig.Output.Verbosity = 'Detailed' + $pesterConfig.TestResult.Enabled = $true + $pesterConfig.TestResult.OutputFormat = 'NUnitXml' + $pesterConfig.TestResult.OutputPath = Join-Path $testResultsPath "macOSPackage.xml" + $result = Invoke-Pester -Configuration $pesterConfig + if ($result.FailedCount -gt 0) { + throw "Package validation failed with $($result.FailedCount) failed test(s)" + } + shell: pwsh + - name: Publish and Upload Pester Test Results + if: always() + uses: "./.github/actions/test/process-pester-results" + with: + name: "macOSPackage" + testResultsFolder: "${{ runner.workspace }}/testResults" + - name: Upload package artifact + if: always() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: macos-package + path: "*.pkg" ready_to_merge: name: macos ready to merge needs: - - verify_xunit + - xunit_tests - PackageMac-macos_packaging - macos_test_elevated_ci - macos_test_elevated_others - macos_test_unelevated_ci - macos_test_unelevated_others if: always() - uses: PowerShell/compliance/.github/workflows/ready-to-merge.yml@v1.0.0 + uses: PowerShell/compliance/.github/workflows/ready-to-merge.yml@c8b3ad5819ad7078f3e375519b4f8c6232d1cbdf # v1.0.0 with: needs_context: ${{ toJson(needs) }} diff --git a/.github/workflows/markdownLink.yml b/.github/workflows/markdownLink.yml deleted file mode 100644 index 26ea99cc2ef..00000000000 --- a/.github/workflows/markdownLink.yml +++ /dev/null @@ -1,51 +0,0 @@ -on: - pull_request: - branches: - - master - -name: Check modified markdown files -permissions: - contents: read - -jobs: - markdown-link-check: - runs-on: ubuntu-latest - if: github.repository_owner == 'PowerShell' - - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: gaurav-nelson/github-action-markdown-link-check@5c5dfc0ac2e225883c0e5f03a85311ec2830d368 # v1 - with: - use-quiet-mode: 'yes' - use-verbose-mode: 'yes' - check-modified-files-only: 'yes' - config-file: .github/workflows/markdown-link/config.json - markdown-lint: - permissions: - contents: read - packages: read - statuses: write - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - # Full git history is needed to get a proper - # list of changed files within `super-linter` - fetch-depth: 0 - - name: Load super-linter configuration - # Use grep inverse matching to exclude eventual comments in the .env file - # because the GitHub Actions command to set environment variables doesn't - # support comments. - # Ref: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions#setting-an-environment-variable - run: grep -v '^#' tools/super-linter/config/super-linter.env >> "$GITHUB_ENV" - - name: Lint Markdown - uses: super-linter/super-linter@5119dcd8011e92182ce8219d9e9efc82f16fddb6 # v8.0.0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Super-Linter correction instructions - if: failure() - uses: actions/github-script@v7.0.1 - with: - script: | - const message = "Super-Linter found issues in the changed files. Please check the logs for details. You can run the linter locally using the command: `./tools/super-lister/super-lister.ps1`."; - core.setFailed(message); diff --git a/.github/workflows/markdownLinkDaily.yml b/.github/workflows/markdownLinkDaily.yml deleted file mode 100644 index 87cdbdfd122..00000000000 --- a/.github/workflows/markdownLinkDaily.yml +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT license. - -name: PowerShell Daily Markdown Link Verification - -on: - workflow_dispatch: - schedule: - # At 13:00 UTC every day. - - cron: '0 13 * * *' - -permissions: - contents: read - -jobs: - markdown-link-check: - runs-on: ubuntu-latest - if: github.repository == 'PowerShell/PowerShell' - steps: - - name: Checkout - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - name: Check Links - uses: gaurav-nelson/github-action-markdown-link-check@5c5dfc0ac2e225883c0e5f03a85311ec2830d368 # v1 - with: - use-quiet-mode: 'yes' - use-verbose-mode: 'yes' - config-file: .github/workflows/markdown-link/config.json - - name: Microsoft Teams Notifier - uses: skitionek/notify-microsoft-teams@e7a2493ac87dad8aa7a62f079f295e54ff511d88 # master - if: failure() - with: - webhook_url: ${{ secrets.PS_BUILD_TEAMS_CHANNEL }} - overwrite: "{title: `Failure in .github/markdownLinkDaily.yml validating links. Look at ${workflow_link}`}" diff --git a/.github/workflows/processReminders.yml b/.github/workflows/processReminders.yml deleted file mode 100644 index 18de16159ac..00000000000 --- a/.github/workflows/processReminders.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: 'Process reminders' - -on: - schedule: - - cron: '*/15 * * * *' - workflow_dispatch: - -permissions: - contents: read - -jobs: - reminder: - if: github.repository_owner == 'PowerShell' - permissions: - issues: write # for agrc/reminder-action to set reminders on issues - pull-requests: write # for agrc/reminder-action to set reminders on PRs - runs-on: ubuntu-latest - - steps: - - name: check reminders and notify - uses: agrc/reminder-action@3095f64f8f0c26c751bee802cb1008ece5953078 # v1.0.18 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 451944eaee9..935c8ff93bb 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -32,12 +32,12 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 + uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 with: results_file: results.sarif results_format: sarif @@ -59,7 +59,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: SARIF file path: results.sarif @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5 + uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v3.29.5 with: sarif_file: results.sarif diff --git a/.github/workflows/verify-markdown-links.yml b/.github/workflows/verify-markdown-links.yml new file mode 100644 index 00000000000..19da648a959 --- /dev/null +++ b/.github/workflows/verify-markdown-links.yml @@ -0,0 +1,32 @@ +name: Verify Markdown Links + +on: + push: + branches: [ main, master ] + paths: + - '**/*.md' + - '.github/workflows/verify-markdown-links.yml' + - '.github/actions/infrastructure/markdownlinks/**' + pull_request: + branches: [ main, master ] + paths: + - '**/*.md' + schedule: + # Run weekly on Sundays at midnight UTC to catch external link rot + - cron: '0 0 * * 0' + workflow_dispatch: + +jobs: + verify-markdown-links: + name: Verify Markdown Links + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Verify markdown links + id: verify + uses: ./.github/actions/infrastructure/markdownlinks + with: + timeout-sec: 30 + maximum-retry-count: 2 diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index 3aaa19fcdf6..8a57b8b9726 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -8,6 +8,8 @@ on: - github-mirror paths: - "**" + - "*" + - ".globalconfig" - "!.vsts-ci/misc-analysis.yml" - "!.github/ISSUE_TEMPLATE/**" - "!.dependabot/config.yml" @@ -18,11 +20,12 @@ on: - master - release/** - github-mirror + - "*-feature" # Path filters for PRs need to go into the changes job concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }} cancel-in-progress: ${{ contains(github.ref, 'merge')}} permissions: @@ -32,12 +35,14 @@ run-name: "${{ github.ref_name }} - ${{ github.run_number }}" env: DOTNET_CLI_TELEMETRY_OPTOUT: 1 - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 + DOTNET_NOLOGO: 1 GIT_CONFIG_PARAMETERS: "'core.autocrlf=false'" NugetSecurityAnalysisWarningLevel: none POWERSHELL_TELEMETRY_OPTOUT: 1 __SuppressAnsiEscapeSequences: 1 nugetMultiFeedWarnLevel: none + SYSTEM_ARTIFACTSDIRECTORY: ${{ github.workspace }}/artifacts + BUILD_ARTIFACTSTAGINGDIRECTORY: ${{ github.workspace }}/artifacts jobs: changes: name: Change Detection @@ -51,9 +56,11 @@ jobs: # Set job outputs to values from filter step outputs: source: ${{ steps.filter.outputs.source }} + buildModuleChanged: ${{ steps.filter.outputs.buildModuleChanged }} + packagingChanged: ${{ steps.filter.outputs.packagingChanged }} steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Change Detection id: filter @@ -64,11 +71,11 @@ jobs: ci_build: name: Build PowerShell needs: changes - if: ${{ needs.changes.outputs.source == 'true' }} + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} runs-on: windows-latest steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1000 - name: Build @@ -78,11 +85,11 @@ jobs: needs: - ci_build - changes - if: ${{ needs.changes.outputs.source == 'true' }} + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} runs-on: windows-latest steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1000 - name: Windows Unelevated CI @@ -90,16 +97,17 @@ jobs: with: purpose: UnelevatedPesterTests tagSet: CI + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} windows_test_elevated_ci: name: Windows Elevated CI needs: - ci_build - changes - if: ${{ needs.changes.outputs.source == 'true' }} + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} runs-on: windows-latest steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1000 - name: Windows Elevated CI @@ -107,16 +115,17 @@ jobs: with: purpose: ElevatedPesterTests tagSet: CI + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} windows_test_unelevated_others: name: Windows Unelevated Others needs: - ci_build - changes - if: ${{ needs.changes.outputs.source == 'true' }} + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} runs-on: windows-latest steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1000 - name: Windows Unelevated Others @@ -124,16 +133,17 @@ jobs: with: purpose: UnelevatedPesterTests tagSet: Others + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} windows_test_elevated_others: name: Windows Elevated Others needs: - ci_build - changes - if: ${{ needs.changes.outputs.source == 'true' }} + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} runs-on: windows-latest steps: - name: checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 1000 - name: Windows Elevated Others @@ -141,29 +151,44 @@ jobs: with: purpose: ElevatedPesterTests tagSet: Others - verify_xunit: - name: Verify xUnit test results + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + xunit_tests: + name: xUnit Tests needs: - - ci_build - changes - if: ${{ needs.changes.outputs.source == 'true' }} - runs-on: windows-latest - steps: - - name: checkout - uses: actions/checkout@v4 - with: - fetch-depth: 1000 - - name: Verify xUnit test results - uses: "./.github/actions/test/verify_xunit" + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} + uses: ./.github/workflows/xunit-tests.yml + with: + runner_os: windows-latest + test_results_artifact_name: testResults-xunit + analyze: + name: CodeQL Analysis + needs: changes + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} + uses: ./.github/workflows/analyze-reusable.yml + permissions: + actions: read + contents: read + security-events: write + with: + runner_os: windows-latest + windows_packaging: + name: Windows Packaging + needs: + - changes + if: ${{ needs.changes.outputs.packagingChanged == 'true' }} + uses: ./.github/workflows/windows-packaging-reusable.yml ready_to_merge: name: windows ready to merge needs: - - verify_xunit + - xunit_tests - windows_test_elevated_ci - windows_test_elevated_others - windows_test_unelevated_ci - windows_test_unelevated_others + - analyze + - windows_packaging if: always() - uses: PowerShell/compliance/.github/workflows/ready-to-merge.yml@v1.0.0 + uses: PowerShell/compliance/.github/workflows/ready-to-merge.yml@c8b3ad5819ad7078f3e375519b4f8c6232d1cbdf # v1.0.0 with: needs_context: ${{ toJson(needs) }} diff --git a/.github/workflows/windows-packaging-reusable.yml b/.github/workflows/windows-packaging-reusable.yml new file mode 100644 index 00000000000..8d0255d4443 --- /dev/null +++ b/.github/workflows/windows-packaging-reusable.yml @@ -0,0 +1,92 @@ +name: Windows Packaging (Reusable) + +on: + workflow_call: + +env: + GIT_CONFIG_PARAMETERS: "'core.autocrlf=false'" + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + POWERSHELL_TELEMETRY_OPTOUT: 1 + DOTNET_NOLOGO: 1 + __SuppressAnsiEscapeSequences: 1 + nugetMultiFeedWarnLevel: none + SYSTEM_ARTIFACTSDIRECTORY: ${{ github.workspace }}/artifacts + BUILD_ARTIFACTSTAGINGDIRECTORY: ${{ github.workspace }}/artifacts + +permissions: + contents: read + +jobs: + package: + name: ${{ matrix.architecture }} - ${{ matrix.channel }} + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + include: + - architecture: x64 + channel: preview + runtimePrefix: win7 + - architecture: x86 + channel: stable + runtimePrefix: win7 + - architecture: x86 + channel: preview + runtimePrefix: win7 + - architecture: arm64 + channel: preview + runtimePrefix: win + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + + - name: Capture Environment + if: success() || failure() + run: | + Import-Module .\tools\ci.psm1 + Show-Environment + shell: pwsh + + - name: Capture PowerShell Version Table + if: success() || failure() + run: | + $PSVersionTable + shell: pwsh + + - name: Switch to Public Feeds + if: success() + run: | + Import-Module .\tools\ci.psm1 + Switch-PSNugetConfig -Source Public + shell: pwsh + + - name: Setup .NET + uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 + with: + global-json-file: ./global.json + + - name: Bootstrap + if: success() + run: | + Import-Module .\tools\ci.psm1 + Invoke-CIInstall -SkipUser + shell: pwsh + + - name: Build and Package + run: | + Import-Module .\tools\ci.psm1 + New-CodeCoverageAndTestPackage + Invoke-CIFinish -Runtime ${{ matrix.runtimePrefix }}-${{ matrix.architecture }} -channel ${{ matrix.channel }} + shell: pwsh + + - name: Upload Build Artifacts + if: always() + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: windows-packaging-${{ matrix.architecture }}-${{ matrix.channel }} + path: | + ${{ github.workspace }}/artifacts/**/* + !${{ github.workspace }}/artifacts/**/*.pdb diff --git a/.github/workflows/xunit-tests.yml b/.github/workflows/xunit-tests.yml new file mode 100644 index 00000000000..c643917edd0 --- /dev/null +++ b/.github/workflows/xunit-tests.yml @@ -0,0 +1,56 @@ +name: xUnit Tests (Reusable) + +on: + workflow_call: + inputs: + runner_os: + description: 'Runner OS for xUnit tests' + type: string + required: false + default: ubuntu-latest + test_results_artifact_name: + description: 'Artifact name for xUnit test results directory' + type: string + required: false + default: testResults-xunit + +permissions: + contents: read + +jobs: + xunit: + name: Run xUnit Tests + runs-on: ${{ inputs.runner_os }} + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + + - name: Setup .NET + uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 + with: + global-json-file: ./global.json + + - name: Bootstrap + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Invoke-CIInstall -SkipUser + Sync-PSTags -AddRemoteIfMissing + + - name: Build PowerShell and run xUnit tests + shell: pwsh + run: | + Import-Module ./tools/ci.psm1 + Start-PSBuild + Write-Host "Running full xUnit test suite (no skipping)..." + Invoke-CIxUnit + Write-Host "Completed xUnit test run." + + - name: Upload xUnit results + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + if: always() + with: + name: ${{ inputs.test_results_artifact_name }} + path: ${{ github.workspace }}/xUnitTestResults.xml diff --git a/.gitignore b/.gitignore index ccadde27182..48556cf1b8c 100644 --- a/.gitignore +++ b/.gitignore @@ -83,6 +83,9 @@ TestsResults*.xml ParallelXUnitResults.xml xUnitResults.xml +# Attack Surface Analyzer results +asa-results/ + # Resharper settings PowerShell.sln.DotSettings.user *.msp @@ -116,5 +119,8 @@ assets/manpage/*.gz tmp/* .env.local +# Pester test failure analysis results (generated by analyze-pr-test-failures.ps1) +**/pester-analysis-*/ + # Ignore CTRF report files crtf/* diff --git a/.globalconfig b/.globalconfig index d51e5cfacfa..e0dd4ccb9e5 100644 --- a/.globalconfig +++ b/.globalconfig @@ -215,7 +215,7 @@ dotnet_diagnostic.CA1070.severity = warning # CA1200: Avoid using cref tags with a prefix # https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1200 -dotnet_diagnostic.CA1200.severity = silent +dotnet_diagnostic.CA1200.severity = warning # CA1303: Do not pass literals as localized parameters # https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1303 @@ -510,6 +510,34 @@ dotnet_diagnostic.CA1846.severity = warning # https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1847 dotnet_diagnostic.CA1847.severity = warning +# CA1852: Seal internal types +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1852 +dotnet_diagnostic.CA1852.severity = warning + +# CA1853: Unnecessary call to 'Dictionary.ContainsKey(key)' +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1853 +dotnet_diagnostic.CA1853.severity = warning + +# CA1858: Use 'StartsWith' instead of 'IndexOf' +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1858 +dotnet_diagnostic.CA1858.severity = warning + +# CA1860: Avoid using 'Enumerable.Any()' extension method +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1860 +dotnet_diagnostic.CA1860.severity = warning + +# CA1865: Use char overload +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1865 +dotnet_diagnostic.CA1865.severity = warning + +# CA1866: Use char overload +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1866 +dotnet_diagnostic.CA1866.severity = warning + +# CA1867: Use char overload +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1867 +dotnet_diagnostic.CA1867.severity = warning + # CA1868: Unnecessary call to 'Contains' for sets # https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1868 dotnet_diagnostic.CA1868.severity = warning @@ -558,6 +586,14 @@ dotnet_diagnostic.CA2015.severity = warning # https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2016 dotnet_diagnostic.CA2016.severity = suggestion +# CA2021: Do not call Enumerable.Cast or Enumerable.OfType with incompatible types +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2021 +dotnet_diagnostic.CA2021.severity = warning + +# CA2022: Avoid inexact read with 'Stream.Read' +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2022 +dotnet_diagnostic.CA2022.severity = warning + # CA2100: Review SQL queries for security vulnerabilities # https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2100 dotnet_diagnostic.CA2100.severity = none @@ -1206,7 +1242,7 @@ dotnet_diagnostic.IDE0018.severity = silent # IDE0019: InlineAsTypeCheck # https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0019 -dotnet_diagnostic.IDE0019.severity = silent +dotnet_diagnostic.IDE0019.severity = warning # IDE0020: InlineIsTypeCheck # https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0020 @@ -1326,7 +1362,7 @@ dotnet_diagnostic.IDE0048.severity = suggestion # IDE0049: PreferBuiltInOrFrameworkType # https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0049 -dotnet_diagnostic.IDE0049.severity = warning +dotnet_diagnostic.IDE0049.severity = suggestion # IDE0050: ConvertAnonymousTypeToTuple # https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0050 @@ -1438,7 +1474,7 @@ dotnet_diagnostic.IDE0079.severity = silent # IDE0080: RemoveConfusingSuppressionForIsExpression # https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0080 -dotnet_diagnostic.IDE0080.severity = silent +dotnet_diagnostic.IDE0080.severity = warning # IDE0081: RemoveUnnecessaryByVal # https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0081 @@ -1834,7 +1870,7 @@ dotnet_diagnostic.SA1205.severity = warning # SA1206: Declaration keywords should follow order # https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1206.md -dotnet_diagnostic.SA1206.severity = none +dotnet_diagnostic.SA1206.severity = warning # SA1207: Protected should come before internal # https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1207.md diff --git a/.pipelines/EV2Specs/ServiceGroupRoot/ServiceModel.json b/.pipelines/EV2Specs/ServiceGroupRoot/ServiceModel.json index 00555349c35..ce974fe69e5 100644 --- a/.pipelines/EV2Specs/ServiceGroupRoot/ServiceModel.json +++ b/.pipelines/EV2Specs/ServiceGroupRoot/ServiceModel.json @@ -17,8 +17,8 @@ { "type": "Run", "properties": { - "imageName": "adm-mariner-20-l", - "imageVersion": "v11" + "imageName": "adm-azurelinux-30-l", + "imageVersion": "v2" } } ] diff --git a/.pipelines/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1 b/.pipelines/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1 index 25a5686b33e..6797ff94575 100644 --- a/.pipelines/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1 +++ b/.pipelines/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1 @@ -64,7 +64,7 @@ function Get-MappedRepositoryIds { $repoIds.AddRange(([string[]]$repos.id)) } else { - Write-Failure "Could not find repo for $urlGlob" + throw "Could not find repo for $urlGlob" } if ($repoIds.Count -gt 0) { @@ -354,6 +354,9 @@ try { $skipPublish = $metadataContent.SkipPublish $lts = $metadataContent.LTS + # Check if this is a rebuild version (e.g., 7.4.13-rebuild.5) + $isRebuild = $releaseVersion -match '-rebuild\.' + if ($releaseVersion.Contains('-')) { $channel = 'preview' $packageNames = @('powershell-preview') @@ -363,7 +366,8 @@ try { $packageNames = @('powershell') } - if ($lts) { + # Only add LTS package if not a rebuild branch + if ($lts -and -not $isRebuild) { $packageNames += @('powershell-lts') } diff --git a/.pipelines/MSIXBundle-vPack-Official.yml b/.pipelines/MSIXBundle-vPack-Official.yml index f20e8a31114..2461d3cd310 100644 --- a/.pipelines/MSIXBundle-vPack-Official.yml +++ b/.pipelines/MSIXBundle-vPack-Official.yml @@ -1,57 +1,75 @@ trigger: none +pr: none parameters: # parameters are shown up in ADO UI in a build queue time - name: 'createVPack' displayName: 'Create and Submit VPack' type: boolean default: true +- name: 'ReleaseTagVar' + type: string + displayName: 'Release Tag Var:' + default: 'fromBranch' - name: 'debug' displayName: 'Enable debug output' type: boolean default: false -- name: 'ReleaseTagVar' +- name: netiso + displayName: "Network Isolation Policy" type: string - displayName: 'Release Tag Var:' - default: 'fromBranch' + values: + - KS4 + - R1 + - Netlock + default: "R1" -name: msixbundle_vPack_$(date:yyMM).$(date:dd)$(rev:rrr) +name: msixbundle_vPack_$(Build.SourceBranchName)_Prod.True_Create.${{ parameters.createVPack }}_$(date:yyyyMMdd).$(rev:rr) variables: - CDP_DEFINITION_BUILD_COUNT: $[counter('', 0)] - system.debug: ${{ parameters.debug }} - BuildSolution: $(Build.SourcesDirectory)\dirs.proj - ReleaseTagVar: ${{ parameters.ReleaseTagVar }} - BuildConfiguration: Release - WindowsContainerImage: 'onebranch.azurecr.io/windows/ltsc2019/vse2022:latest' - Codeql.Enabled: false # pipeline is not building artifacts; it repackages existing artifacts into a vpack - DOTNET_CLI_TELEMETRY_OPTOUT: 1 - POWERSHELL_TELEMETRY_OPTOUT: 1 + - name: CDP_DEFINITION_BUILD_COUNT + value: $[counter('', 0)] + - name: system.debug + value: ${{ parameters.debug }} + - name: BuildSolution + value: $(Build.SourcesDirectory)\dirs.proj + - name: BuildConfiguration + value: Release + - name: WindowsContainerImage + value: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' + - name: Codeql.Enabled + value: false # pipeline is not building artifacts; it repackages existing artifacts into a vpack + - name: DOTNET_CLI_TELEMETRY_OPTOUT + value: 1 + - name: POWERSHELL_TELEMETRY_OPTOUT + value: 1 + - name: nugetMultiFeedWarnLevel + value: none + - name: ReleaseTagVar + value: ${{ parameters.ReleaseTagVar }} + - name: netiso + value: ${{ parameters.netiso }} + - group: certificate_logical_to_actual # used within signing task + - group: MSIXSigningProfile + - group: msixTools resources: repositories: - - repository: templates + - repository: onebranchTemplates type: git name: OneBranch.Pipelines/GovernedTemplates ref: refs/heads/main - pipelines: - - pipeline: PSPackagesOfficial - source: 'PowerShell-Packages-Official' - trigger: - branches: - include: - - master - - releases/* - extends: - template: v2/Microsoft.Official.yml@templates + template: v2/Microsoft.Official.yml@onebranchTemplates parameters: platform: name: 'windows_undocked' # windows undocked - + featureFlags: + WindowsHostVersion: + Version: 2022 + Network: ${{ variables.netiso }} cloudvault: enabled: false - globalSdl: useCustomPolicy: true # for signing code disableLegacyManifest: true @@ -68,80 +86,329 @@ extends: suppressionsFile: $(Build.SourcesDirectory)\.config\suppress.json binskim: enabled: false + exactToolVersion: 4.4.2 # APIScan requires a non-Ready-To-Run build apiscan: enabled: false - asyncSDL: - enabled: false tsaOptionsFile: .config/tsaoptions.json stages: - - stage: build + - stage: Build_MSIX_Package + displayName: 'Build and create MSIX packages' + dependsOn: [] jobs: - - job: main + - job: Build pool: type: windows + strategy: + matrix: + x64: + Architecture: x64 + arm64: + Architecture: arm64 + variables: + ArtifactPlatform: 'windows' ob_outputDirectory: '$(BUILD.SOURCESDIRECTORY)\out' - ob_createvpack_enabled: ${{ parameters.createVPack }} - ob_createvpack_packagename: 'PowerShell.app' - ob_createvpack_owneralias: 'dongbow' - ob_createvpack_description: 'VPack for the PowerShell Application' - ob_createvpack_targetDestinationDirectory: '$(Destination)' - ob_createvpack_propsFile: false - ob_createvpack_provData: true - ob_createvpack_metadata: '$(Build.SourceVersion)' - ob_createvpack_versionAs: string - ob_createvpack_version: '$(version)' - ob_createvpack_verbose: true + ob_artifactBaseName: drop_build_$(Architecture) steps: - - template: .pipelines/templates/SetVersionVariables.yml@self - parameters: - ReleaseTagVar: $(ReleaseTagVar) - UseJson: no - - - pwsh: | - Write-Verbose -Verbose 'PowerShell Version: $(version)' - if('$(version)' -match '-') { - throw "Don't release a preview build msixbundle package" - } - displayName: Stop any preview release - - - download: PSPackagesOfficial - artifact: 'drop_msixbundle_CreateMSIXBundle' - displayName: Download package - - - pwsh: | - $payloadDir = '$(Pipeline.Workspace)\PSPackagesOfficial\drop_msixbundle_CreateMSIXBundle' - Get-ChildItem $payloadDir -Recurse | Out-String -Width 150 - $vstsCommandString = "vso[task.setvariable variable=PayloadDir]$payloadDir" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - displayName: 'Capture Artifact Listing' - - - pwsh: | - $bundlePackage = Get-ChildItem '$(PayloadDir)\*.msixbundle' - Write-Verbose -Verbose ("MSIX bundle package: " + $bundlePackage.FullName -join ', ') - if ($bundlePackage.Count -ne 1) { - throw "Expected to find 1 MSIX bundle package, but found $($bundlePackage.Count)" - } + - checkout: self + displayName: Checkout source code - during restore + clean: true + path: s ## $(Build.SourcesDirectory) is at '$(Pipeline.Workspace)\s', so we need to check out repo to the 's' folder. + env: + ob_restore_phase: true + + # The env variable 'ReleaseTagVar' will be updated in this step. + - template: /.pipelines/templates/SetVersionVariables.yml@self + parameters: + ReleaseTagVar: $(ReleaseTagVar) + CreateJson: yes + + - pwsh: | + $releaseTag = '$(ReleaseTagVar)' + if ($releaseTag -match '-') { + throw "Never release msixbundle vpack for a preview build. Current version: $releaseTag" + } + + # Check if release tag matches the expected format v#.#.# + $matched = $releaseTag -match '^v\d+\.(\d+)\.\d+$' + if (-not $matched) { + throw "Release tag must be in the format v#.#.#, such as 'v7.4.3'. Current version: $releaseTag" + } + displayName: Stop any preview release + env: + ob_restore_phase: true + + ### START BUILD ### + + # Clone the checked out PowerShell repo to '/PowerShell' and set the variable 'PowerShellRoot'. + - template: /.pipelines/templates/cloneToOfficialPath.yml@self + + - template: /.pipelines/templates/insert-nuget-config-azfeed.yml@self + parameters: + repoRoot: $(PowerShellRoot) - if (-not (Test-Path '$(ob_outputDirectory)' -PathType Container)) { - $null = New-Item '$(ob_outputDirectory)' -ItemType Directory -ErrorAction Stop + # Add CodeQL Init task right before your 'Build' step. + - task: CodeQL3000Init@0 + env: + ob_restore_phase: true # Set ob_restore_phase to run this step before '🔒 Setup Signing' step. + inputs: + Enabled: true + # AnalyzeInPipeline: false = upload results + # AnalyzeInPipeline: true = do not upload results + AnalyzeInPipeline: false + Language: csharp + + - template: /.pipelines/templates/install-dotnet.yml@self + + - pwsh: | + $runtime = switch ($env:Architecture) + { + "x64" { "win7-x64" } + "arm64" { "win-arm64" } } - $targetPath = Join-Path '$(ob_outputDirectory)' 'Microsoft.PowerShell_8wekyb3d8bbwe.msixbundle' - Copy-Item -Verbose -Path $bundlePackage.FullName -Destination $targetPath - displayName: 'Stage msixbundle for vpack' + $vstsCommandString = "vso[task.setvariable variable=Runtime]$runtime" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + + Write-Verbose -Message "Building PowerShell with Runtime: $runtime for '$env:BuildConfiguration' configuration" + Import-Module -Name $(PowerShellRoot)/build.psm1 -Force + $buildWithSymbolsPath = New-Item -ItemType Directory -Path $(Pipeline.Workspace)/Symbols_$(Architecture) -Force + + Start-PSBootstrap -Scenario Package + $null = New-Item -ItemType Directory -Path $buildWithSymbolsPath -Force -Verbose + + Start-PSBuild -Runtime $runtime -Configuration Release -Output $buildWithSymbolsPath -Clean -PSModuleRestore -ReleaseTag $(ReleaseTagVar) - - pwsh: | - Write-Verbose "VPack Version: $(ob_createvpack_version)" -Verbose - $vpackFiles = Get-ChildItem -Path $(ob_outputDirectory)\* -Recurse - if($vpackFiles.Count -eq 0) { - throw "No files found in $(ob_outputDirectory)" + $refFolderPath = Join-Path $buildWithSymbolsPath 'ref' + Write-Verbose -Verbose "refFolderPath: $refFolderPath" + $outputPath = Join-Path '$(ob_outputDirectory)' 'psoptions' + $null = New-Item -ItemType Directory -Path $outputPath -Force + $psOptPath = "$outputPath/psoptions.json" + Save-PSOptions -PSOptionsPath $psOptPath + + Write-Verbose -Verbose "Verifying pdbs exist in build folder" + $pdbs = Get-ChildItem -Path $buildWithSymbolsPath -Recurse -Filter *.pdb + if ($pdbs.Count -eq 0) { + throw "No pdbs found in build folder" + } + else { + Write-Verbose -Verbose "Found $($pdbs.Count) pdbs in build folder" + $pdbs | ForEach-Object { + Write-Verbose -Verbose "Pdb: $($_.FullName)" } - $vpackFiles | Out-String -Width 150 - displayName: Debug Output Directory and Version - condition: succeededOrFailed() + + $pdbs | Compress-Archive -DestinationPath '$(ob_outputDirectory)\symbols-$(Architecture).zip' -Update + } + + Write-Verbose -Verbose "Completed building PowerShell for '$env:BuildConfiguration' configuration" + displayName: 'Build Windows Universal - $(Architecture)-$(BuildConfiguration) Symbols folder' + env: + ob_restore_phase: true # Set ob_restore_phase to run this step before '🔒 Setup Signing' step. + + # Add CodeQL Finalize task right after your 'Build' step. + - task: CodeQL3000Finalize@0 + env: + ob_restore_phase: true # Set ob_restore_phase to run this step before '🔒 Setup Signing' step. + + - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 + displayName: 'Component Detection' + inputs: + sourceScanPath: '$(PowerShellRoot)\src' + ob_restore_phase: true + + # The signed files will be put in '$(ob_outputDirectory)\Signed-$(Runtime)' after this step. + - template: /.pipelines/templates/obp-file-signing.yml@self + parameters: + binPath: '$(Pipeline.Workspace)/Symbols_$(Architecture)' + OfficialBuild: true + + ### END OF BUILD ### + + - pwsh: | + Get-ChildItem -Path '$(ob_outputDirectory)\Signed-$(Runtime)' -Recurse | Out-String -Width 9999 + displayName: Capture signed files + condition: succeededOrFailed() + + - pwsh: | + Get-ChildItem -Path env: | Out-String -Width 9999 + displayName: Capture Environment + condition: succeededOrFailed() + + ### START Packaging ### + + - template: /.pipelines/templates/shouldSign.yml@self + parameters: + ob_restore_phase: false + + - pwsh: | + Write-Verbose -Verbose "runtime = '$(Runtime)'" + Write-Verbose -Verbose "RepoRoot = '$(PowerShellRoot)'" + + $runtime = '$(Runtime)' + $repoRoot = '$(PowerShellRoot)' + Import-Module "$repoRoot\build.psm1" + Import-Module "$repoRoot\tools\packaging" + + Find-Dotnet + + $signedFilesPath = '$(ob_outputDirectory)\Signed-$(Runtime)' + $psoptionsFilePath = '$(ob_outputDirectory)\psoptions\psoptions.json' + + Write-Verbose -Verbose "signedFilesPath: $signedFilesPath" + Write-Verbose -Verbose "psoptionsFilePath: $psoptionsFilePath" + + Write-Verbose -Message "checking pwsh exists in $signedFilesPath" -Verbose + if (-not (Test-Path $signedFilesPath\pwsh.exe)) { + throw "pwsh.exe not found in $signedFilesPath" + } + + Write-Verbose -Message "Restoring PSOptions from $psoptionsFilePath" -Verbose + + Restore-PSOptions -PSOptionsPath "$psoptionsFilePath" + Get-PSOptions | Write-Verbose -Verbose + + $metadata = Get-Content "$repoRoot\tools\metadata.json" -Raw | ConvertFrom-Json + Write-Verbose -Verbose "metadata:" + $metadata | Out-String | Write-Verbose -Verbose + + $publishLTS = $metadata.LTSRelease.PublishToChannels + $publishStable = $metadata.StableRelease.PublishToChannels + + Write-Verbose -Verbose "Publish LTS: $publishLTS" + Write-Verbose -Verbose "Publish Stable: $publishStable" + + if (-not $publishLTS -and -not $publishStable) { + throw "metadata.json indicates no channels to publish to." + } + + ## Generated packages are placed in the current directory by default. + Set-Location $repoRoot + Start-PSPackage -Type msix -SkipReleaseChecks -WindowsRuntime $runtime -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath -LTS:$publishLTS + + if ($publishLTS -and $publishStable) { + $enabledChannels = "LTS,Stable" + Write-Verbose -Verbose "Publish to both LTS and Stable channels. Building additional Stable MSIX." + Start-PSPackage -Type msix -SkipReleaseChecks -WindowsRuntime $runtime -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath + } + + $msixPkgNameFilter = "PowerShell*.msix" + $msixPkgFile = Get-ChildItem -Path $repoRoot -Filter $msixPkgNameFilter -Recurse -File | ForEach-Object FullName + Write-Verbose -Verbose "Unsigned msix package(s): $msixPkgFile" + + $pkgDir = '$(ob_outputDirectory)\pkgs' + $null = New-Item -ItemType Directory -Path $pkgDir -Force + Copy-Item -Path $msixPkgFile -Destination $pkgDir -Force -Verbose + + if (-not $enabledChannels) { + $enabledChannels = $publishLTS ? 'LTS' : ($publishStable ? 'Stable' : 'None') + } + + ## Create an output variable for the enabled channels so that downstream stages can use it. + $vstsCommandString = "vso[task.setvariable variable=EnabledChannels;isOutput=true]$enabledChannels" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + name: BuildMSIXPackage + displayName: 'Build MSIX Package (Unsigned)' + + ### END OF Packaging ### + + - pwsh: | + Get-ChildItem -Path '$(ob_outputDirectory)\pkgs' -Recurse + displayName: 'List Unsigned Package' + + - pwsh: | + $signedFilesPath = '$(ob_outputDirectory)\Signed-$(Runtime)' + Remove-Item -Path $signedFilesPath -Recurse -Force -Verbose + displayName: 'Remove Signed-$(Runtime) folder' + + - stage: Pack_MSIXBundle_And_Sign + displayName: 'Pack and sign MSIXBundle' + dependsOn: [Build_MSIX_Package] + + variables: + EnabledChannels: $[ stageDependencies.Build_MSIX_Package.Build.outputs['x64.BuildMSIXPackage.EnabledChannels'] ] + + jobs: + - template: /.pipelines/templates/create-msixbundle-vpack.yml@self + parameters: + Channel: 'LTS' + createVPack: ${{ parameters.createVPack }} + + - template: /.pipelines/templates/create-msixbundle-vpack.yml@self + parameters: + Channel: 'Stable' + createVPack: ${{ parameters.createVPack }} + + - stage: Publish_Symbols + displayName: 'Publish Symbols' + dependsOn: [Pack_MSIXBundle_And_Sign] + jobs: + - job: PublishSymbols + pool: + type: windows + variables: + ob_outputDirectory: '$(BUILD.SOURCESDIRECTORY)\out' + + steps: + - checkout: self + displayName: Checkout source code - during restore + clean: true + path: s ## $(Build.SourcesDirectory) is at '$(Pipeline.Workspace)\s', so we need to check out repo to the 's' folder. + env: + ob_restore_phase: true + + - pwsh: | + Get-ChildItem Env: | Out-String -Width 9999 + displayName: 'Capture Environment Variables' + + - task: DownloadPipelineArtifact@2 + inputs: + artifactName: drop_build_x64 + itemPattern: | + **/symbols-*.zip + targetPath: '$(Build.ArtifactStagingDirectory)\downloads' + displayName: Download symbols for x64 + + - task: DownloadPipelineArtifact@2 + inputs: + artifactName: drop_build_arm64 + itemPattern: | + **/symbols-*.zip + targetPath: '$(Build.ArtifactStagingDirectory)\downloads' + displayName: Download symbols for arm64 + + - pwsh: | + $downloadDir = '$(Build.ArtifactStagingDirectory)\downloads' + Write-Verbose -Verbose "Enumerating $downloadDir" + $downloadedArtifacts = Get-ChildItem -Path $downloadDir -Recurse -Filter 'symbols-*.zip' + $downloadedArtifacts | Out-String -Width 9999 + + $expandedRoot = New-Item -Path "$(Pipeline.Workspace)\expanded" -ItemType Directory -Verbose + $downloadedArtifacts | ForEach-Object { + $expandDir = Join-Path $expandedRoot $_.BaseName + Write-Verbose -Verbose "Expanding $($_.FullName) to $expandDir" + $null = New-Item -Path $expandDir -ItemType Directory -Verbose + Expand-Archive -Path $_.FullName -DestinationPath $expandDir -Force + } + + Write-Verbose -Verbose "Enumerating $expandedRoot" + Get-ChildItem -Path $expandedRoot -Recurse | Out-String -Width 9999 + $vstsCommandString = "vso[task.setvariable variable=SymbolsPath]$expandedRoot" + Write-Verbose -Message "$vstsCommandString" -Verbose + Write-Host -Object "##$vstsCommandString" + displayName: Expand and capture symbols folders + + - task: PublishSymbols@2 + condition: and(succeeded(), ${{ parameters.createVPack }}) + inputs: + symbolsFolder: '$(SymbolsPath)' + searchPattern: '**/*.pdb' + indexSources: false + publishSymbols: true + symbolServerType: TeamServices + detailedLog: true diff --git a/.pipelines/NonOfficial/PowerShell-Coordinated_Packages-NonOfficial.yml b/.pipelines/NonOfficial/PowerShell-Coordinated_Packages-NonOfficial.yml new file mode 100644 index 00000000000..69506750c34 --- /dev/null +++ b/.pipelines/NonOfficial/PowerShell-Coordinated_Packages-NonOfficial.yml @@ -0,0 +1,98 @@ +trigger: none + +parameters: + - name: InternalSDKBlobURL + displayName: URL to the blob having internal .NET SDK + type: string + default: ' ' + - name: ReleaseTagVar + displayName: Release Tag + type: string + default: 'fromBranch' + - name: SKIP_SIGNING + displayName: Debugging - Skip Signing + type: string + default: 'NO' + - name: RUN_TEST_AND_RELEASE + displayName: Debugging - Run Test and Release Artifacts Stage + type: boolean + default: true + - name: RUN_WINDOWS + displayName: Debugging - Enable Windows Stage + type: boolean + default: true + - name: ENABLE_MSBUILD_BINLOGS + displayName: Debugging - Enable MSBuild Binary Logs + type: boolean + default: false + - name: FORCE_CODEQL + displayName: Debugging - Enable CodeQL and set cadence to 1 hour + type: boolean + default: false + +name: bins-$(BUILD.SOURCEBRANCHNAME)-nonofficial-$(Build.BuildId) + +resources: + repositories: + - repository: ComplianceRepo + type: github + endpoint: ComplianceGHRepo + name: PowerShell/compliance + ref: master + - repository: onebranchTemplates + type: git + name: OneBranch.Pipelines/GovernedTemplates + ref: refs/heads/main + +variables: + - template: /.pipelines/templates/variables/PowerShell-Coordinated_Packages-Variables.yml@self + parameters: + InternalSDKBlobURL: ${{ parameters.InternalSDKBlobURL }} + ReleaseTagVar: ${{ parameters.ReleaseTagVar }} + SKIP_SIGNING: ${{ parameters.SKIP_SIGNING }} + ENABLE_MSBUILD_BINLOGS: ${{ parameters.ENABLE_MSBUILD_BINLOGS }} + FORCE_CODEQL: ${{ parameters.FORCE_CODEQL }} + +extends: + template: v2/OneBranch.NonOfficial.CrossPlat.yml@onebranchTemplates + parameters: + customTags: 'ES365AIMigrationTooling' + featureFlags: + LinuxHostVersion: + Network: KS3 + WindowsHostVersion: + Version: 2022 + Network: KS3 + incrementalSDLBinaryAnalysis: true + globalSdl: + disableLegacyManifest: true + # disabled Armorty as we dont have any ARM templates to scan. It fails on some sample ARM templates. + armory: + enabled: false + sbom: + enabled: true + codeql: + compiled: + enabled: $(CODEQL_ENABLED) + tsaEnabled: true # This enables TSA bug filing only for CodeQL 3000 + credscan: + enabled: true + scanFolder: $(Build.SourcesDirectory) + suppressionsFile: $(Build.SourcesDirectory)\.config\suppress.json + cg: + enabled: true + ignoreDirectories: '.devcontainer,demos,docker,docs,src,test,tools/packaging' + binskim: + enabled: false + exactToolVersion: 4.4.2 + # APIScan requires a non-Ready-To-Run build + apiscan: + enabled: false + tsaOptionsFile: .config\tsaoptions.json + + stages: + - template: /.pipelines/templates/stages/PowerShell-Coordinated_Packages-Stages.yml@self + parameters: + RUN_WINDOWS: ${{ parameters.RUN_WINDOWS }} + RUN_TEST_AND_RELEASE: ${{ parameters.RUN_TEST_AND_RELEASE }} + OfficialBuild: false diff --git a/.pipelines/NonOfficial/PowerShell-Packages-NonOfficial.yml b/.pipelines/NonOfficial/PowerShell-Packages-NonOfficial.yml new file mode 100644 index 00000000000..0993cd69546 --- /dev/null +++ b/.pipelines/NonOfficial/PowerShell-Packages-NonOfficial.yml @@ -0,0 +1,97 @@ +trigger: none + +parameters: # parameters are shown up in ADO UI in a build queue time + - name: ForceAzureBlobDelete + displayName: Delete Azure Blob + type: string + values: + - true + - false + default: false + - name: 'debug' + displayName: 'Enable debug output' + type: boolean + default: false + - name: InternalSDKBlobURL + displayName: URL to the blob having internal .NET SDK + type: string + default: ' ' + - name: ReleaseTagVar + displayName: Release Tag + type: string + default: 'fromBranch' + - name: SKIP_SIGNING + displayName: Skip Signing + type: string + default: 'NO' + - name: disableNetworkIsolation + type: boolean + default: false + +name: pkgs-$(BUILD.SOURCEBRANCHNAME)-nonofficial-$(Build.BuildId) + +variables: + - template: /.pipelines/templates/variables/PowerShell-Packages-Variables.yml@self + parameters: + debug: ${{ parameters.debug }} + ForceAzureBlobDelete: ${{ parameters.ForceAzureBlobDelete }} + ReleaseTagVar: ${{ parameters.ReleaseTagVar }} + disableNetworkIsolation: ${{ parameters.disableNetworkIsolation }} + +resources: + pipelines: + - pipeline: CoOrdinatedBuildPipeline + source: 'PowerShell-Coordinated_Packages-NonOfficial' + trigger: + branches: + include: + - master + - releases/* + + repositories: + - repository: onebranchTemplates + type: git + name: OneBranch.Pipelines/GovernedTemplates + ref: refs/heads/main + +extends: + template: v2/OneBranch.NonOfficial.CrossPlat.yml@onebranchTemplates + parameters: + cloudvault: + enabled: false + featureFlags: + WindowsHostVersion: + Version: 2022 + Network: KS3 + LinuxHostVersion: + Network: KS3 + linuxEsrpSigning: true + incrementalSDLBinaryAnalysis: true + disableNetworkIsolation: ${{ variables.disableNetworkIsolation }} + globalSdl: + disableLegacyManifest: true + # disabled Armorty as we dont have any ARM templates to scan. It fails on some sample ARM templates. + armory: + enabled: false + sbom: + enabled: true + compiled: + enabled: false + credscan: + enabled: true + scanFolder: $(Build.SourcesDirectory) + suppressionsFile: $(Build.SourcesDirectory)\.config\suppress.json + cg: + enabled: true + ignoreDirectories: '.devcontainer,demos,docker,docs,src,test,tools/packaging' + binskim: + enabled: false + exactToolVersion: 4.4.2 + # APIScan requires a non-Ready-To-Run build + apiscan: + enabled: false + tsaOptionsFile: .config\tsaoptions.json + stages: + - template: /.pipelines/templates/stages/PowerShell-Packages-Stages.yml@self + parameters: + OfficialBuild: false diff --git a/.pipelines/NonOfficial/PowerShell-Release-Azure-NonOfficial.yml b/.pipelines/NonOfficial/PowerShell-Release-Azure-NonOfficial.yml new file mode 100644 index 00000000000..b0bb4d79b39 --- /dev/null +++ b/.pipelines/NonOfficial/PowerShell-Release-Azure-NonOfficial.yml @@ -0,0 +1,82 @@ +trigger: none + +parameters: # parameters are shown up in ADO UI in a build queue time + - name: 'debug' + displayName: 'Enable debug output' + type: boolean + default: false + - name: skipPublish + displayName: Skip PMC Publish + type: boolean + default: false + - name: SKIP_SIGNING + displayName: Skip Signing + type: string + default: 'NO' + +name: ev2-$(BUILD.SOURCEBRANCHNAME)-nonofficial-$(Build.BuildId) + +variables: + - template: /.pipelines/templates/variables/PowerShell-Release-Azure-Variables.yml@self + parameters: + debug: ${{ parameters.debug }} + +resources: + repositories: + - repository: onebranchTemplates + type: git + name: OneBranch.Pipelines/GovernedTemplates + ref: refs/heads/main + + pipelines: + - pipeline: CoOrdinatedBuildPipeline + source: 'PowerShell-Coordinated_Packages-NonOfficial' + + - pipeline: PSPackagesOfficial + source: 'PowerShell-Packages-NonOfficial' + trigger: + branches: + include: + - master + - releases/* + +extends: + template: v2/OneBranch.NonOfficial.CrossPlat.yml@onebranchTemplates + parameters: + featureFlags: + WindowsHostVersion: + Version: 2022 + Network: Netlock + linuxEsrpSigning: true + incrementalSDLBinaryAnalysis: true + cloudvault: + enabled: false + globalSdl: + disableLegacyManifest: true + # disabled Armory as we dont have any ARM templates to scan. It fails on some sample ARM templates. + armory: + enabled: false + tsa: + enabled: true + credscan: + enabled: true + scanFolder: $(Build.SourcesDirectory) + suppressionsFile: $(Build.SourcesDirectory)\.config\suppress.json + binskim: + break: false # always break the build on binskim issues in addition to TSA upload + exactToolVersion: 4.4.2 + policheck: + break: true # always break the build on policheck issues. You can disable it by setting to 'false' + tsaOptionsFile: $(Build.SourcesDirectory)\.config\tsaoptions.json + stages: + - template: /.pipelines/templates/release-prep-for-ev2.yml@self + parameters: + skipPublish: ${{ parameters.skipPublish }} + + # NonOfficial: run the publish stage to verify templateContext artifact download, + # but skip the actual Ev2 push to PMC. + - template: /.pipelines/templates/release-publish-pmc.yml@self + parameters: + releaseEnvironment: Test + stagePrefix: Test + skipEv2Push: true diff --git a/.pipelines/NonOfficial/PowerShell-Release-NonOfficial.yml b/.pipelines/NonOfficial/PowerShell-Release-NonOfficial.yml new file mode 100644 index 00000000000..ebfc599e42a --- /dev/null +++ b/.pipelines/NonOfficial/PowerShell-Release-NonOfficial.yml @@ -0,0 +1,106 @@ +trigger: none + +parameters: # parameters are shown up in ADO UI in a build queue time + - name: 'debug' + displayName: 'Enable debug output' + type: boolean + default: false + - name: InternalSDKBlobURL + displayName: URL to the blob having internal .NET SDK + type: string + default: ' ' + - name: ReleaseTagVar + displayName: Release Tag + type: string + default: 'fromBranch' + - name: SKIP_SIGNING + displayName: Skip Signing + type: string + default: 'NO' + - name: SkipPublish + displayName: Skip Publishing to Nuget + type: boolean + default: false + - name: SkipPSInfraInstallers + displayName: Skip Copying Archives and Installers to PSInfrastructure Public Location + type: boolean + default: false + - name: skipMSIXPublish + displayName: Skip MSIX Publish + type: boolean + default: false + +name: release-$(BUILD.SOURCEBRANCHNAME)-nonofficial-$(Build.BuildId) + +variables: + - template: /.pipelines/templates/variables/PowerShell-Release-Variables.yml@self + parameters: + debug: ${{ parameters.debug }} + ReleaseTagVar: ${{ parameters.ReleaseTagVar }} + +resources: + repositories: + - repository: onebranchTemplates + type: git + name: OneBranch.Pipelines/GovernedTemplates + ref: refs/heads/main + - repository: PSInternalTools + type: git + name: PowerShellCore/Internal-PowerShellTeam-Tools + ref: refs/heads/master + + pipelines: + - pipeline: CoOrdinatedBuildPipeline + source: 'PowerShell-Coordinated_Packages-NonOfficial' + + # NOTE: The alias name "PSPackagesOfficial" is intentionally reused here even + # for the NonOfficial pipeline source. Downstream shared templates (for example, + # release-validate-sdk.yml and release-upload-buildinfo.yml) reference artifacts + # using `download: PSPackagesOfficial`, so changing this alias would break them. + - pipeline: PSPackagesOfficial + source: 'PowerShell-Packages-NonOfficial' + trigger: + branches: + include: + - master + - releases/* + +extends: + template: v2/OneBranch.NonOfficial.CrossPlat.yml@onebranchTemplates + parameters: + release: + category: NonAzure + featureFlags: + WindowsHostVersion: + Version: 2022 + Network: KS3 + incrementalSDLBinaryAnalysis: true + cloudvault: + enabled: false + globalSdl: + disableLegacyManifest: true + # disabled Armory as we dont have any ARM templates to scan. It fails on some sample ARM templates. + armory: + enabled: false + tsa: + enabled: true + credscan: + enabled: true + scanFolder: $(Build.SourcesDirectory) + suppressionsFile: $(Build.SourcesDirectory)\.config\suppress.json + binskim: + break: false # always break the build on binskim issues in addition to TSA upload + exactToolVersion: 4.4.2 + policheck: + break: true # always break the build on policheck issues. You can disable it by setting to 'false' + # suppression: + # suppressionFile: $(Build.SourcesDirectory)\.gdn\global.gdnsuppress + tsaOptionsFile: .config\tsaoptions.json + + stages: + - template: /.pipelines/templates/stages/PowerShell-Release-Stages.yml@self + parameters: + releaseEnvironment: Test + SkipPublish: ${{ parameters.SkipPublish }} + SkipPSInfraInstallers: ${{ parameters.SkipPSInfraInstallers }} + skipMSIXPublish: ${{ parameters.skipMSIXPublish }} diff --git a/.pipelines/NonOfficial/PowerShell-vPack-NonOfficial.yml b/.pipelines/NonOfficial/PowerShell-vPack-NonOfficial.yml new file mode 100644 index 00000000000..071db02cff8 --- /dev/null +++ b/.pipelines/NonOfficial/PowerShell-vPack-NonOfficial.yml @@ -0,0 +1,88 @@ +trigger: none + +parameters: # parameters are shown up in ADO UI in a build queue time +- name: 'createVPack' + displayName: 'Create and Submit VPack' + type: boolean + default: true +- name: vPackName + type: string + displayName: 'VPack Name:' + default: 'PowerShell.BuildTool' + values: + - PowerShell.BuildTool + - PowerShell + - PowerShellDoNotUse +- name: 'ReleaseTagVar' + type: string + displayName: 'Release Tag Var:' + default: 'fromBranch' +- name: 'debug' + displayName: 'Enable debug output' + type: boolean + default: false +- name: netiso + displayName: "Network Isolation Policy" + type: string + values: + - KS4 + - R1 + - Netlock + default: "R1" + +name: vPack_$(Build.SourceBranchName)_NonOfficial_Create.${{ parameters.createVPack }}_Name.${{ parameters.vPackName}}_$(date:yyyyMMdd).$(rev:rr) + +variables: + - template: /.pipelines/templates/variables/PowerShell-vPack-Variables.yml@self + parameters: + debug: ${{ parameters.debug }} + ReleaseTagVar: ${{ parameters.ReleaseTagVar }} + netiso: ${{ parameters.netiso }} + +resources: + repositories: + - repository: onebranchTemplates + type: git + name: OneBranch.Pipelines/GovernedTemplates + ref: refs/heads/main + +extends: + template: v2/Microsoft.NonOfficial.yml@onebranchTemplates + parameters: + platform: + name: 'windows_undocked' # windows undocked + + featureFlags: + WindowsHostVersion: + Version: 2022 + Network: ${{ variables.netiso }} + + cloudvault: + enabled: false + + globalSdl: + useCustomPolicy: true # for signing code + disableLegacyManifest: true + # disabled Armory as we dont have any ARM templates to scan. It fails on some sample ARM templates. + armory: + enabled: false + sbom: + enabled: true + compiled: + enabled: false + credscan: + enabled: true + scanFolder: $(Build.SourcesDirectory) + suppressionsFile: $(Build.SourcesDirectory)\.config\suppress.json + binskim: + enabled: false + exactToolVersion: 4.4.2 + # APIScan requires a non-Ready-To-Run build + apiscan: + enabled: false + tsaOptionsFile: .config/tsaoptions.json + stages: + - template: /.pipelines/templates/stages/PowerShell-vPack-Stages.yml@self + parameters: + createVPack: ${{ parameters.createVPack }} + vPackName: ${{ parameters.vPackName }} diff --git a/.pipelines/PowerShell-Coordinated_Packages-Official.yml b/.pipelines/PowerShell-Coordinated_Packages-Official.yml index 902c31f8a96..82f129a0a5e 100644 --- a/.pipelines/PowerShell-Coordinated_Packages-Official.yml +++ b/.pipelines/PowerShell-Coordinated_Packages-Official.yml @@ -1,4 +1,3 @@ -name: bins-$(BUILD.SOURCEBRANCHNAME)-$(Build.BuildId) trigger: none parameters: @@ -31,6 +30,8 @@ parameters: type: boolean default: false +name: bins-$(BUILD.SOURCEBRANCHNAME)-prod-$(Build.BuildId) + resources: repositories: - repository: ComplianceRepo @@ -44,50 +45,13 @@ resources: ref: refs/heads/main variables: - - name: PS_RELEASE_BUILD - value: 1 - - name: DOTNET_CLI_TELEMETRY_OPTOUT - value: 1 - - name: POWERSHELL_TELEMETRY_OPTOUT - value: 1 - - name: nugetMultiFeedWarnLevel - value: none - - name: NugetSecurityAnalysisWarningLevel - value: none - - name: skipNugetSecurityAnalysis - value: true - - name: branchCounterKey - value: $[format('{0:yyyyMMdd}-{1}', pipeline.startTime,variables['Build.SourceBranch'])] - - name: branchCounter - value: $[counter(variables['branchCounterKey'], 1)] - - name: BUILDSECMON_OPT_IN - value: true - - name: __DOTNET_RUNTIME_FEED - value: ${{ parameters.InternalSDKBlobURL }} - - name: LinuxContainerImage - value: onebranch.azurecr.io/linux/ubuntu-2004:latest - - name: WindowsContainerImage - value: onebranch.azurecr.io/windows/ltsc2019/vse2022:latest - - name: CDP_DEFINITION_BUILD_COUNT - value: $[counter('', 0)] - - name: ReleaseTagVar - value: ${{ parameters.ReleaseTagVar }} - - name: SKIP_SIGNING - value: ${{ parameters.SKIP_SIGNING }} - - group: mscodehub-feed-read-general - - group: mscodehub-feed-read-akv - - name: ENABLE_MSBUILD_BINLOGS - value: ${{ parameters.ENABLE_MSBUILD_BINLOGS }} - - ${{ if eq(parameters['FORCE_CODEQL'],'true') }}: - # Cadence is hours before CodeQL will allow a re-upload of the database - - name: CodeQL.Cadence - value: 1 - - name: CODEQL_ENABLED - ${{ if or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(parameters['FORCE_CODEQL'],'true')) }}: - value: true - ${{ else }}: - value: false - + - template: templates/variables/PowerShell-Coordinated_Packages-Variables.yml + parameters: + InternalSDKBlobURL: ${{ parameters.InternalSDKBlobURL }} + ReleaseTagVar: ${{ parameters.ReleaseTagVar }} + SKIP_SIGNING: ${{ parameters.SKIP_SIGNING }} + ENABLE_MSBUILD_BINLOGS: ${{ parameters.ENABLE_MSBUILD_BINLOGS }} + FORCE_CODEQL: ${{ parameters.FORCE_CODEQL }} extends: template: v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates @@ -97,7 +61,9 @@ extends: LinuxHostVersion: Network: KS3 WindowsHostVersion: + Version: 2022 Network: KS3 + incrementalSDLBinaryAnalysis: true globalSdl: disableLegacyManifest: true # disabled Armorty as we dont have any ARM templates to scan. It fails on some sample ARM templates. @@ -116,190 +82,17 @@ extends: cg: enabled: true ignoreDirectories: '.devcontainer,demos,docker,docs,src,test,tools/packaging' - asyncSdl: - enabled: true - forStages: [prep, macos, linux, windows, test_and_release_artifacts] - credscan: - enabled: true - scanFolder: $(Build.SourcesDirectory) - suppressionsFile: $(Build.SourcesDirectory)\PowerShell\.config\suppress.json - binskim: - enabled: false - # APIScan requires a non-Ready-To-Run build - apiscan: - enabled: false - tsaOptionsFile: .config\tsaoptions.json + binskim: + enabled: false + exactToolVersion: 4.4.2 + # APIScan requires a non-Ready-To-Run build + apiscan: + enabled: false + tsaOptionsFile: .config\tsaoptions.json stages: - - stage: prep - jobs: - - job: SetVars - displayName: Set Variables - pool: - type: windows - - variables: - - name: ob_outputDirectory - value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT/BuildJson' - - name: ob_sdl_codeSignValidation_enabled - value: false - - name: ob_sdl_codeql_compiled_enabled - value: false - - name: ob_sdl_credscan_suppressionsFile - value: $(Build.SourcesDirectory)\PowerShell\.config\suppress.json - - name: ob_sdl_tsa_configFile - value: $(Build.SourcesDirectory)\PowerShell\.config\tsaoptions.json - - name: ob_signing_setup_enabled - value: false - - name: ob_sdl_sbom_enabled - value: false - - steps: - - checkout: self - clean: true - env: - ob_restore_phase: true # This ensures checkout is done at the beginning of the restore phase - - - pwsh: | - Get-ChildItem Env: | Out-String -width 9999 -Stream | write-Verbose -Verbose - displayName: Capture environment variables - env: - ob_restore_phase: true # This ensures checkout is done at the beginning of the restore phase - - - template: /.pipelines/templates/SetVersionVariables.yml@self - parameters: - ReleaseTagVar: $(ReleaseTagVar) - CreateJson: yes - UseJson: no - - - stage: macos - displayName: macOS - build and sign - dependsOn: ['prep'] - jobs: - - template: /.pipelines/templates/mac.yml@self - parameters: - buildArchitecture: x64 - - template: /.pipelines/templates/mac.yml@self - parameters: - buildArchitecture: arm64 - - - stage: linux - displayName: linux - build and sign - dependsOn: ['prep'] - jobs: - - template: /.pipelines/templates/linux.yml@self - parameters: - Runtime: 'linux-x64' - JobName: 'linux_x64' - - - template: /.pipelines/templates/linux.yml@self - parameters: - Runtime: 'linux-x64' - JobName: 'linux_x64_minSize' - BuildConfiguration: 'minSize' - - - template: /.pipelines/templates/linux.yml@self - parameters: - Runtime: 'linux-arm' - JobName: 'linux_arm' - - - template: /.pipelines/templates/linux.yml@self - parameters: - Runtime: 'linux-arm64' - JobName: 'linux_arm64' - - - template: /.pipelines/templates/linux.yml@self - parameters: - Runtime: 'fxdependent-linux-x64' - JobName: 'linux_fxd_x64_mariner' - - - template: /.pipelines/templates/linux.yml@self - parameters: - Runtime: 'fxdependent-linux-arm64' - JobName: 'linux_fxd_arm64_mariner' - - - template: /.pipelines/templates/linux.yml@self - parameters: - Runtime: 'fxdependent-noopt-linux-musl-x64' - JobName: 'linux_fxd_x64_alpine' - - - template: /.pipelines/templates/linux.yml@self - parameters: - Runtime: 'fxdependent' - JobName: 'linux_fxd' - - - template: /.pipelines/templates/linux.yml@self - parameters: - Runtime: 'linux-musl-x64' - JobName: 'linux_x64_alpine' - - - stage: windows - displayName: windows - build and sign - dependsOn: ['prep'] - condition: and(succeeded(),eq('${{ parameters.RUN_WINDOWS }}','true')) - jobs: - - template: /.pipelines/templates/windows-hosted-build.yml@self - parameters: - Architecture: x64 - BuildConfiguration: release - JobName: build_windows_x64_release - - template: /.pipelines/templates/windows-hosted-build.yml@self - parameters: - Architecture: x64 - BuildConfiguration: minSize - JobName: build_windows_x64_minSize_release - - template: /.pipelines/templates/windows-hosted-build.yml@self - parameters: - Architecture: x86 - JobName: build_windows_x86_release - - template: /.pipelines/templates/windows-hosted-build.yml@self - parameters: - Architecture: arm64 - JobName: build_windows_arm64_release - - template: /.pipelines/templates/windows-hosted-build.yml@self - parameters: - Architecture: fxdependent - JobName: build_windows_fxdependent_release - - template: /.pipelines/templates/windows-hosted-build.yml@self - parameters: - Architecture: fxdependentWinDesktop - JobName: build_windows_fxdependentWinDesktop_release - - - stage: test_and_release_artifacts - displayName: Test and Release Artifacts - dependsOn: ['prep'] - condition: and(succeeded(),eq('${{ parameters.RUN_TEST_AND_RELEASE }}','true')) - jobs: - - template: /.pipelines/templates/testartifacts.yml@self - - - job: release_json - displayName: Create and Upload release.json - pool: - type: windows - variables: - - name: ob_outputDirectory - value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' - - name: ob_sdl_tsa_configFile - value: $(Build.SourcesDirectory)\PowerShell\.config\tsaoptions.json - - name: ob_sdl_credscan_suppressionsFile - value: $(Build.SourcesDirectory)\PowerShell\.config\suppress.json - steps: - - checkout: self - clean: true - - template: /.pipelines/templates/SetVersionVariables.yml@self - parameters: - ReleaseTagVar: $(ReleaseTagVar) - - powershell: | - $metadata = Get-Content '$(Build.SourcesDirectory)/PowerShell/tools/metadata.json' -Raw | ConvertFrom-Json - $LTS = $metadata.LTSRelease.Package - @{ ReleaseVersion = "$(Version)"; LTSRelease = $LTS } | ConvertTo-Json | Out-File "$(Build.StagingDirectory)\release.json" - Get-Content "$(Build.StagingDirectory)\release.json" - - if (-not (Test-Path "$(ob_outputDirectory)\metadata")) { - New-Item -ItemType Directory -Path "$(ob_outputDirectory)\metadata" - } - - Copy-Item -Path "$(Build.StagingDirectory)\release.json" -Destination "$(ob_outputDirectory)\metadata" -Force - displayName: Create and upload release.json file to build artifact - retryCountOnTaskFailure: 2 - - template: /.pipelines/templates/step/finalize.yml@self + - template: templates/stages/PowerShell-Coordinated_Packages-Stages.yml + parameters: + RUN_WINDOWS: ${{ parameters.RUN_WINDOWS }} + RUN_TEST_AND_RELEASE: ${{ parameters.RUN_TEST_AND_RELEASE }} + OfficialBuild: true diff --git a/.pipelines/PowerShell-Packages-Official.yml b/.pipelines/PowerShell-Packages-Official.yml index 487e8cb9c6a..8afce29ede7 100644 --- a/.pipelines/PowerShell-Packages-Official.yml +++ b/.pipelines/PowerShell-Packages-Official.yml @@ -24,43 +24,19 @@ parameters: # parameters are shown up in ADO UI in a build queue time displayName: Skip Signing type: string default: 'NO' - -name: pkgs-$(BUILD.SOURCEBRANCHNAME)-$(Build.BuildId) + - name: disableNetworkIsolation + type: boolean + default: false + +name: pkgs-$(BUILD.SOURCEBRANCHNAME)-prod-$(Build.BuildId) variables: - - name: CDP_DEFINITION_BUILD_COUNT - value: $[counter('', 0)] # needed for onebranch.pipeline.version task - - name: system.debug - value: ${{ parameters.debug }} - - name: ENABLE_PRS_DELAYSIGN - value: 1 - - name: ROOT - value: $(Build.SourcesDirectory) - - name: ForceAzureBlobDelete - value: ${{ parameters.ForceAzureBlobDelete }} - - name: NUGET_XMLDOC_MODE - value: none - - name: nugetMultiFeedWarnLevel - value: none - - name: NugetSecurityAnalysisWarningLevel - value: none - - name: skipNugetSecurityAnalysis - value: true - - name: ReleaseTagVar - value: ${{ parameters.ReleaseTagVar }} - - name: ob_outputDirectory - value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' - - name: WindowsContainerImage - value: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' # Docker image which is used to build the project - - name: LinuxContainerImage - value: mcr.microsoft.com/onebranch/cbl-mariner/build:2.0 - - group: mscodehub-feed-read-general - - group: mscodehub-feed-read-akv - - name: branchCounterKey - value: $[format('{0:yyyyMMdd}-{1}', pipeline.startTime,variables['Build.SourceBranch'])] - - name: branchCounter - value: $[counter(variables['branchCounterKey'], 1)] - - group: MSIXSigningProfile + - template: templates/variables/PowerShell-Packages-Variables.yml + parameters: + debug: ${{ parameters.debug }} + ForceAzureBlobDelete: ${{ parameters.ForceAzureBlobDelete }} + ReleaseTagVar: ${{ parameters.ReleaseTagVar }} + disableNetworkIsolation: ${{ parameters.disableNetworkIsolation }} resources: pipelines: @@ -73,13 +49,13 @@ resources: - releases/* repositories: - - repository: templates + - repository: onebranchTemplates type: git name: OneBranch.Pipelines/GovernedTemplates ref: refs/heads/main extends: - template: v2/OneBranch.Official.CrossPlat.yml@templates + template: v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates parameters: cloudvault: enabled: false @@ -87,7 +63,11 @@ extends: WindowsHostVersion: Version: 2022 Network: KS3 + LinuxHostVersion: + Network: KS3 linuxEsrpSigning: true + incrementalSDLBinaryAnalysis: true + disableNetworkIsolation: ${{ variables.disableNetworkIsolation }} globalSdl: disableLegacyManifest: true # disabled Armorty as we dont have any ARM templates to scan. It fails on some sample ARM templates. @@ -104,156 +84,14 @@ extends: cg: enabled: true ignoreDirectories: '.devcontainer,demos,docker,docs,src,test,tools/packaging' - asyncSdl: - enabled: true - forStages: ['build'] - credscan: - enabled: true - scanFolder: $(Build.SourcesDirectory) - suppressionsFile: $(Build.SourcesDirectory)\PowerShell\.config\suppress.json - binskim: - enabled: false - # APIScan requires a non-Ready-To-Run build - apiscan: - enabled: false - tsaOptionsFile: .config\tsaoptions.json + binskim: + enabled: false + exactToolVersion: 4.4.2 + # APIScan requires a non-Ready-To-Run build + apiscan: + enabled: false + tsaOptionsFile: .config\tsaoptions.json stages: - - stage: prep - jobs: - - template: /.pipelines/templates/checkAzureContainer.yml@self - - - stage: mac_package - dependsOn: [prep] - jobs: - - template: /.pipelines/templates/mac-package-build.yml@self - parameters: - buildArchitecture: x64 - - - template: /.pipelines/templates/mac-package-build.yml@self - parameters: - buildArchitecture: arm64 - - - stage: windows_package - dependsOn: [prep] - jobs: - - template: /.pipelines/templates/windows-package-build.yml@self - parameters: - runtime: x64 - - - template: /.pipelines/templates/windows-package-build.yml@self - parameters: - runtime: arm64 - - - template: /.pipelines/templates/windows-package-build.yml@self - parameters: - runtime: x86 - - - template: /.pipelines/templates/windows-package-build.yml@self - parameters: - runtime: fxdependent - - - template: /.pipelines/templates/windows-package-build.yml@self - parameters: - runtime: fxdependentWinDesktop - - - template: /.pipelines/templates/windows-package-build.yml@self - parameters: - runtime: minsize - - - stage: linux_package - dependsOn: [prep] - jobs: - - template: /.pipelines/templates/linux-package-build.yml@self - parameters: - unsignedDrop: 'drop_linux_build_linux_x64' - signedDrop: 'drop_linux_sign_linux_x64' - packageType: deb - jobName: deb - - - template: /.pipelines/templates/linux-package-build.yml@self - parameters: - unsignedDrop: 'drop_linux_build_linux_fxd_x64_mariner' - signedDrop: 'drop_linux_sign_linux_fxd_x64_mariner' - packageType: rpm-fxdependent #mariner-x64 - jobName: mariner_x64 - signingProfile: 'CP-459159-pgpdetached' - - - template: /.pipelines/templates/linux-package-build.yml@self - parameters: - unsignedDrop: 'drop_linux_build_linux_fxd_arm64_mariner' - signedDrop: 'drop_linux_sign_linux_fxd_arm64_mariner' - packageType: rpm-fxdependent-arm64 #mariner-arm64 - jobName: mariner_arm64 - signingProfile: 'CP-459159-pgpdetached' - - - template: /.pipelines/templates/linux-package-build.yml@self - parameters: - unsignedDrop: 'drop_linux_build_linux_x64' - signedDrop: 'drop_linux_sign_linux_x64' - packageType: rpm - jobName: rpm - - - template: /.pipelines/templates/linux-package-build.yml@self - parameters: - unsignedDrop: 'drop_linux_build_linux_arm' - signedDrop: 'drop_linux_sign_linux_arm' - packageType: tar-arm - jobName: tar_arm - - - template: /.pipelines/templates/linux-package-build.yml@self - parameters: - unsignedDrop: 'drop_linux_build_linux_arm64' - signedDrop: 'drop_linux_sign_linux_arm64' - packageType: tar-arm64 - jobName: tar_arm64 - - - template: /.pipelines/templates/linux-package-build.yml@self - parameters: - unsignedDrop: 'drop_linux_build_linux_x64_alpine' - signedDrop: 'drop_linux_sign_linux_x64_alpine' - packageType: tar-alpine - jobName: tar_alpine - - - template: /.pipelines/templates/linux-package-build.yml@self - parameters: - unsignedDrop: 'drop_linux_build_linux_fxd' - signedDrop: 'drop_linux_sign_linux_fxd' - packageType: fxdependent - jobName: fxdependent - - - template: /.pipelines/templates/linux-package-build.yml@self - parameters: - unsignedDrop: 'drop_linux_build_linux_x64' - signedDrop: 'drop_linux_sign_linux_x64' - packageType: tar - jobName: tar - - - template: /.pipelines/templates/linux-package-build.yml@self - parameters: - unsignedDrop: 'drop_linux_build_linux_fxd_x64_alpine' - signedDrop: 'drop_linux_sign_linux_fxd_x64_alpine' - packageType: tar-alpine-fxdependent - jobName: tar_alpine_fxd - - - template: /.pipelines/templates/linux-package-build.yml@self - parameters: - unsignedDrop: 'drop_linux_build_linux_x64_minSize' - signedDrop: 'drop_linux_sign_linux_x64_minSize' - packageType: min-size - jobName: minSize - - - stage: nupkg - dependsOn: [prep] - jobs: - - template: /.pipelines/templates/nupkg.yml@self - - - stage: msixbundle - displayName: 'Create MSIX Bundle' - dependsOn: [windows_package] - jobs: - - template: /.pipelines/templates/package-create-msix.yml@self - - - stage: upload - dependsOn: [mac_package, windows_package, linux_package, nupkg, msixbundle] - jobs: - - template: /.pipelines/templates/uploadToAzure.yml@self + - template: templates/stages/PowerShell-Packages-Stages.yml + parameters: + OfficialBuild: true diff --git a/.pipelines/PowerShell-Release-Official-Azure.yml b/.pipelines/PowerShell-Release-Official-Azure.yml index 2d644c7a5dd..b5f57438925 100644 --- a/.pipelines/PowerShell-Release-Official-Azure.yml +++ b/.pipelines/PowerShell-Release-Official-Azure.yml @@ -14,42 +14,16 @@ parameters: # parameters are shown up in ADO UI in a build queue time type: string default: 'NO' -name: ev2-$(BUILD.SOURCEBRANCHNAME)-$(Build.BuildId) +name: ev2-$(BUILD.SOURCEBRANCHNAME)-prod-$(Build.BuildId) variables: - - name: CDP_DEFINITION_BUILD_COUNT - value: $[counter('', 0)] - - name: system.debug - value: ${{ parameters.debug }} - - name: ENABLE_PRS_DELAYSIGN - value: 1 - - name: ROOT - value: $(Build.SourcesDirectory) - - name: REPOROOT - value: $(Build.SourcesDirectory) - - name: OUTPUTROOT - value: $(REPOROOT)\out - - name: NUGET_XMLDOC_MODE - value: none - - name: nugetMultiFeedWarnLevel - value: none - - name: NugetSecurityAnalysisWarningLevel - value: none - - name: skipNugetSecurityAnalysis - value: true - - name: ob_outputDirectory - value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' - - name: ob_sdl_tsa_configFile - value: $(Build.SourcesDirectory)\.config\tsaoptions.json - - name: WindowsContainerImage - value: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' - - name: LinuxContainerImage - value: mcr.microsoft.com/onebranch/cbl-mariner/build:2.0 - - group: PoolNames + - template: templates/variables/PowerShell-Release-Azure-Variables.yml + parameters: + debug: ${{ parameters.debug }} resources: repositories: - - repository: templates + - repository: onebranchTemplates type: git name: OneBranch.Pipelines/GovernedTemplates ref: refs/heads/main @@ -67,13 +41,14 @@ resources: - releases/* extends: - template: v2/OneBranch.Official.CrossPlat.yml@templates + template: v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates parameters: featureFlags: WindowsHostVersion: Version: 2022 Network: Netlock linuxEsrpSigning: true + incrementalSDLBinaryAnalysis: true cloudvault: enabled: false globalSdl: @@ -81,9 +56,6 @@ extends: # disabled Armory as we dont have any ARM templates to scan. It fails on some sample ARM templates. armory: enabled: false - asyncSdl: - enabled: true - tsaOptionsFile: .config/tsaoptions.json tsa: enabled: true credscan: @@ -92,9 +64,10 @@ extends: suppressionsFile: $(Build.SourcesDirectory)\.config\suppress.json binskim: break: false # always break the build on binskim issues in addition to TSA upload + exactToolVersion: 4.4.2 policheck: break: true # always break the build on policheck issues. You can disable it by setting to 'false' - tsaOptionsFile: .config\tsaoptions.json + tsaOptionsFile: $(Build.SourcesDirectory)\.config\tsaoptions.json stages: - template: /.pipelines/templates/release-prep-for-ev2.yml@self parameters: diff --git a/.pipelines/PowerShell-Release-Official.yml b/.pipelines/PowerShell-Release-Official.yml index 0c41442da9f..3528e6b1471 100644 --- a/.pipelines/PowerShell-Release-Official.yml +++ b/.pipelines/PowerShell-Release-Official.yml @@ -25,43 +25,22 @@ parameters: # parameters are shown up in ADO UI in a build queue time displayName: Skip Copying Archives and Installers to PSInfrastructure Public Location type: boolean default: false + - name: skipMSIXPublish + displayName: Skip MSIX Publish + type: boolean + default: false -name: release-$(BUILD.SOURCEBRANCHNAME)-$(Build.BuildId) +name: release-$(BUILD.SOURCEBRANCHNAME)-prod-$(Build.BuildId) variables: - - name: CDP_DEFINITION_BUILD_COUNT - value: $[counter('', 0)] - - name: system.debug - value: ${{ parameters.debug }} - - name: ENABLE_PRS_DELAYSIGN - value: 1 - - name: ROOT - value: $(Build.SourcesDirectory) - - name: REPOROOT - value: $(Build.SourcesDirectory) - - name: OUTPUTROOT - value: $(REPOROOT)\out - - name: NUGET_XMLDOC_MODE - value: none - - name: nugetMultiFeedWarnLevel - value: none - - name: NugetSecurityAnalysisWarningLevel - value: none - - name: skipNugetSecurityAnalysis - value: true - - name: ob_outputDirectory - value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' - - name: WindowsContainerImage - value: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' - - name: LinuxContainerImage - value: mcr.microsoft.com/onebranch/cbl-mariner/build:2.0 - - name: ReleaseTagVar - value: ${{ parameters.ReleaseTagVar }} - - group: PoolNames + - template: templates/variables/PowerShell-Release-Variables.yml + parameters: + debug: ${{ parameters.debug }} + ReleaseTagVar: ${{ parameters.ReleaseTagVar }} resources: repositories: - - repository: templates + - repository: onebranchTemplates type: git name: OneBranch.Pipelines/GovernedTemplates ref: refs/heads/main @@ -83,14 +62,15 @@ resources: - releases/* extends: - template: v2/OneBranch.Official.CrossPlat.yml@templates + template: v2/OneBranch.Official.CrossPlat.yml@onebranchTemplates parameters: - release: + release: category: NonAzure featureFlags: WindowsHostVersion: Version: 2022 Network: KS3 + incrementalSDLBinaryAnalysis: true cloudvault: enabled: false globalSdl: @@ -98,9 +78,6 @@ extends: # disabled Armory as we dont have any ARM templates to scan. It fails on some sample ARM templates. armory: enabled: false - asyncSdl: - enabled: true - tsaOptionsFile: .config/tsaoptions.json tsa: enabled: true credscan: @@ -109,6 +86,7 @@ extends: suppressionsFile: $(Build.SourcesDirectory)\.config\suppress.json binskim: break: false # always break the build on binskim issues in addition to TSA upload + exactToolVersion: 4.4.2 policheck: break: true # always break the build on policheck issues. You can disable it by setting to 'false' # suppression: @@ -116,321 +94,9 @@ extends: tsaOptionsFile: .config\tsaoptions.json stages: - - stage: setReleaseTagAndChangelog - displayName: 'Set Release Tag and Upload Changelog' - jobs: - - template: /.pipelines/templates/release-SetTagAndChangelog.yml@self - - - stage: validateSdk - displayName: 'Validate SDK' - dependsOn: [] - jobs: - - template: /.pipelines/templates/release-validate-sdk.yml@self - parameters: - jobName: "windowsSDK" - displayName: "Windows SDK Validation" - imageName: PSMMS2019-Secure - poolName: $(windowsPool) - - - template: /.pipelines/templates/release-validate-sdk.yml@self - parameters: - jobName: "MacOSSDK" - displayName: "MacOS SDK Validation" - imageName: macOS-latest - poolName: Azure Pipelines - - - template: /.pipelines/templates/release-validate-sdk.yml@self - parameters: - jobName: "LinuxSDK" - displayName: "Linux SDK Validation" - imageName: PSMMSUbuntu20.04-Secure - poolName: $(ubuntuPool) - - - stage: gbltool - displayName: 'Validate Global tools' - dependsOn: [] - jobs: - - template: /.pipelines/templates/release-validate-globaltools.yml@self - parameters: - jobName: "WindowsGlobalTools" - displayName: "Windows Global Tools Validation" - jobtype: windows - - - template: /.pipelines/templates/release-validate-globaltools.yml@self - parameters: - jobName: "LinuxGlobalTools" - displayName: "Linux Global Tools Validation" - jobtype: linux - globalToolExeName: 'pwsh' - globalToolPackageName: 'PowerShell.Linux.x64' - - - stage: fxdpackages - displayName: 'Validate FXD Packages' - dependsOn: [] - jobs: - - template: /.pipelines/templates/release-validate-fxdpackages.yml@self - parameters: - jobName: 'winfxd' - displayName: 'Validate Win Fxd Packages' - jobtype: 'windows' - artifactName: 'drop_windows_package_package_win_fxdependent' - packageNamePattern: '**/*win-fxdependent.zip' - - - template: /.pipelines/templates/release-validate-fxdpackages.yml@self - parameters: - jobName: 'winfxdDesktop' - displayName: 'Validate WinDesktop Fxd Packages' - jobtype: 'windows' - artifactName: 'drop_windows_package_package_win_fxdependentWinDesktop' - packageNamePattern: '**/*win-fxdependentwinDesktop.zip' - - - template: /.pipelines/templates/release-validate-fxdpackages.yml@self - parameters: - jobName: 'linuxfxd' - displayName: 'Validate Linux Fxd Packages' - jobtype: 'linux' - artifactName: 'drop_linux_package_fxdependent' - packageNamePattern: '**/*linux-x64-fxdependent.tar.gz' - - - template: /.pipelines/templates/release-validate-fxdpackages.yml@self - parameters: - jobName: 'linuxArm64fxd' - displayName: 'Validate Linux ARM64 Fxd Packages' - jobtype: 'linux' - artifactName: 'drop_linux_package_fxdependent' - # this is really an architecture independent package - packageNamePattern: '**/*linux-x64-fxdependent.tar.gz' - arm64: 'yes' - enableCredScan: false - - - stage: validatePackages - displayName: 'Validate Packages' - dependsOn: [] - jobs: - - template: /.pipelines/templates/release-validate-packagenames.yml@self - - - stage: ManualValidation - dependsOn: [] - displayName: Manual Validation - jobs: - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Validate Windows Packages - jobName: ValidateWinPkg - instructions: | - Validate zip package on windows - - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Validate OSX Packages - jobName: ValidateOsxPkg - instructions: | - Validate tar.gz package on osx-arm64 - - - stage: ReleaseAutomation - dependsOn: [] - displayName: 'Release Automation' - jobs: - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Start Release Automation - jobName: StartRA - instructions: | - Kick off Release automation build at: https://dev.azure.com/powershell-rel/Release-Automation/_build?definitionId=10&_a=summary - - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Triage results - jobName: TriageRA - dependsOnJob: StartRA - instructions: | - Triage ReleaseAutomation results - - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Signoff Tests - dependsOnJob: TriageRA - jobName: SignoffTests - instructions: | - Signoff ReleaseAutomation results - - - stage: UpdateChangeLog - displayName: Update the changelog - dependsOn: - - ManualValidation - - ReleaseAutomation - - validatePackages - - fxdpackages - - gbltool - - validateSdk - - jobs: - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Make sure the changelog is updated - jobName: MergeChangeLog - instructions: | - Update and merge the changelog for the release. - This step is required for creating GitHub draft release. - - - stage: PublishGitHubReleaseAndNuget - displayName: Publish GitHub and Nuget Release - dependsOn: - - setReleaseTagAndChangelog - - UpdateChangeLog - variables: - ob_release_environment: Production - jobs: - - template: /.pipelines/templates/release-githubNuget.yml@self - parameters: - skipPublish: ${{ parameters.SkipPublish }} - - - stage: PushGitTagAndMakeDraftPublic - displayName: Push Git Tag and Make Draft Public - dependsOn: PublishGitHubReleaseAndNuget - jobs: - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Push Git Tag - jobName: PushGitTag - instructions: | - Push the git tag to upstream - - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Make Draft Public - dependsOnJob: PushGitTag - jobName: DraftPublic - instructions: | - Make the GitHub Release Draft Public - - - stage: BlobPublic - displayName: Make Blob Public - dependsOn: - - UpdateChangeLog - - PushGitTagAndMakeDraftPublic - jobs: - - template: /.pipelines/templates/release-MakeBlobPublic.yml@self - parameters: - SkipPSInfraInstallers: ${{ parameters.SkipPSInfraInstallers }} - - - stage: PublishPMC - displayName: Publish PMC - dependsOn: PushGitTagAndMakeDraftPublic - jobs: - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Publish to PMC - jobName: ReleaseToPMC - instructions: | - Run PowerShell-Release-Official-Azure.yml pipeline to publish to PMC - - - stage: UpdateDotnetDocker - dependsOn: PushGitTagAndMakeDraftPublic - displayName: Update DotNet SDK Docker images - jobs: - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Update .NET SDK docker images - jobName: DotnetDocker - instructions: | - Create PR for updating dotnet-docker images to use latest PowerShell version. - 1. Fork and clone https://github.com/dotnet/dotnet-docker.git - 2. git checkout upstream/nightly -b updatePS - 3. dotnet run --project .\eng\update-dependencies\ specific --product-version powershell= --compute-shas - 4. create PR targeting nightly branch - - - stage: UpdateWinGet - dependsOn: PushGitTagAndMakeDraftPublic - displayName: Add manifest entry to winget - jobs: - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Add manifest entry to winget - jobName: UpdateWinGet - instructions: | - This is typically done by the community 1-2 days after the release. - - - stage: PublishMsix - dependsOn: PushGitTagAndMakeDraftPublic - displayName: Publish MSIX to store - jobs: - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Publish the MSIX Bundle package to store - jobName: PublishMsix - instructions: | - Ask Steve to release MSIX bundle package to Store - - - stage: PublishVPack - dependsOn: PushGitTagAndMakeDraftPublic - displayName: Release vPack - jobs: - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Start 2 vPack Release pipelines - jobName: PublishVPack - instructions: | - 1. Kick off PowerShell-vPack-Official pipeline - 2. Kick off PowerShell-MSIXBundle-VPack pipeline - - # Need to verify if the Az PS / CLI team still uses this. Skippinng for this release. - # - stage: ReleaseDeps - # dependsOn: GitHubTasks - # displayName: Update pwsh.deps.json links - # jobs: - # - template: templates/release-UpdateDepsJson.yml - - - stage: UploadBuildInfoJson - dependsOn: PushGitTagAndMakeDraftPublic - displayName: Upload BuildInfo.json - jobs: - - template: /.pipelines/templates/release-upload-buildinfo.yml@self - - - stage: ReleaseSymbols - dependsOn: PushGitTagAndMakeDraftPublic - displayName: Release Symbols - jobs: - - template: /.pipelines/templates/release-symbols.yml@self - - - stage: ChangesToMaster - displayName: Ensure changes are in GH master - dependsOn: - - PublishPMC - jobs: - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Make sure changes are in master - jobName: MergeToMaster - instructions: | - Make sure that changes README.md and metadata.json are merged into master on GitHub. - - - stage: ReleaseToMU - displayName: Release to MU - dependsOn: PushGitTagAndMakeDraftPublic # This only needs the blob to be available - jobs: - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Release to MU - instructions: | - Notify the PM team to start the process of releasing to MU. - - - stage: ReleaseClose - displayName: Finish Release - dependsOn: - - ReleaseToMU - - ReleaseSymbols - jobs: - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Retain Build - jobName: RetainBuild - instructions: | - Retain the build - - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Delete release branch - jobName: DeleteBranch - instructions: | - Delete release + - template: templates/stages/PowerShell-Release-Stages.yml + parameters: + releaseEnvironment: Production + SkipPublish: ${{ parameters.SkipPublish }} + SkipPSInfraInstallers: ${{ parameters.SkipPSInfraInstallers }} + skipMSIXPublish: ${{ parameters.skipMSIXPublish }} diff --git a/.pipelines/PowerShell-vPack-Official.yml b/.pipelines/PowerShell-vPack-Official.yml index 36b6505dd04..13087fbbf65 100644 --- a/.pipelines/PowerShell-vPack-Official.yml +++ b/.pipelines/PowerShell-vPack-Official.yml @@ -5,75 +5,58 @@ parameters: # parameters are shown up in ADO UI in a build queue time displayName: 'Create and Submit VPack' type: boolean default: true -- name: 'debug' - displayName: 'Enable debug output' - type: boolean - default: false -- name: 'architecture' +- name: vPackName type: string - displayName: 'Select the vpack architecture:' + displayName: 'VPack Name:' + default: 'PowerShell.BuildTool' values: - - x64 - - x86 - - arm64 - default: x64 -- name: 'VPackPublishOverride' - type: string - displayName: 'VPack Publish Override Version (can leave blank):' - default: ' ' + - PowerShell.BuildTool + - PowerShell + - PowerShellDoNotUse - name: 'ReleaseTagVar' type: string displayName: 'Release Tag Var:' default: 'fromBranch' +- name: 'debug' + displayName: 'Enable debug output' + type: boolean + default: false +- name: netiso + displayName: "Network Isolation Policy" + type: string + values: + - KS4 + - R1 + - Netlock + default: "R1" -name: vPack_${{ parameters.architecture }}_$(date:yyMM).$(date:dd)$(rev:rrr) +name: vPack_$(Build.SourceBranchName)_Prod_Create.${{ parameters.createVPack }}_Name.${{ parameters.vPackName}}_$(date:yyyyMMdd).$(rev:rr) variables: - - name: CDP_DEFINITION_BUILD_COUNT - value: $[counter('', 0)] - - name: system.debug - value: ${{ parameters.debug }} - - name: BuildSolution - value: $(Build.SourcesDirectory)\dirs.proj - - name: BuildConfiguration - value: Release - - name: WindowsContainerImage - value: 'onebranch.azurecr.io/windows/ltsc2019/vse2022:latest' - - name: Codeql.Enabled - value: false # pipeline is not building artifacts; it repackages existing artifacts into a vpack - - name: DOTNET_CLI_TELEMETRY_OPTOUT - value: 1 - - name: POWERSHELL_TELEMETRY_OPTOUT - value: 1 - - name: nugetMultiFeedWarnLevel - value: none - - name: ReleaseTagVar - value: ${{ parameters.ReleaseTagVar }} - - group: Azure Blob variable group - - group: certificate_logical_to_actual # used within signing task + - template: templates/variables/PowerShell-vPack-Variables.yml + parameters: + debug: ${{ parameters.debug }} + ReleaseTagVar: ${{ parameters.ReleaseTagVar }} + netiso: ${{ parameters.netiso }} resources: repositories: - - repository: templates + - repository: onebranchTemplates type: git name: OneBranch.Pipelines/GovernedTemplates ref: refs/heads/main - pipelines: - - pipeline: PSPackagesOfficial - source: 'PowerShell-Packages-Official' - trigger: - branches: - include: - - master - - releases/* - extends: - template: v2/Microsoft.Official.yml@templates + template: v2/Microsoft.Official.yml@onebranchTemplates parameters: platform: name: 'windows_undocked' # windows undocked + featureFlags: + WindowsHostVersion: + Version: 2022 + Network: ${{ variables.netiso }} + cloudvault: enabled: false @@ -93,132 +76,13 @@ extends: suppressionsFile: $(Build.SourcesDirectory)\.config\suppress.json binskim: enabled: false + exactToolVersion: 4.4.2 # APIScan requires a non-Ready-To-Run build apiscan: enabled: false - asyncSDL: - enabled: false tsaOptionsFile: .config/tsaoptions.json stages: - - stage: main - jobs: - - job: main - pool: - type: windows - - variables: - ob_outputDirectory: '$(BUILD.SOURCESDIRECTORY)\out' - ob_createvpack_enabled: ${{ parameters.createVPack }} - ob_createvpack_packagename: 'PowerShell.${{ parameters.architecture }}' - ob_createvpack_description: PowerShell ${{ parameters.architecture }} $(version) - ob_createvpack_owneralias: tplunk - ob_createvpack_versionAs: string - ob_createvpack_version: '$(version)' - ob_createvpack_propsFile: true - ob_createvpack_verbose: true - - steps: - - template: .pipelines/templates/SetVersionVariables.yml@self - parameters: - ReleaseTagVar: $(ReleaseTagVar) - CreateJson: yes - UseJson: no - - - pwsh: | - if($env:RELEASETAGVAR -match '-') { - throw "Don't release a preview build without coordinating with Windows Engineering Build Tools Team" - } - displayName: Stop any preview release - - - task: UseDotNet@2 - displayName: 'Use .NET Core sdk' - inputs: - packageType: sdk - version: 3.1.x - installationPath: $(Agent.ToolsDirectory)/dotnet - - - pwsh: | - $packageArtifactName = 'drop_windows_package_package_win_${{ parameters.architecture }}' - $vstsCommandString = "vso[task.setvariable variable=PackageArtifactName]$packageArtifactName" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - - $packageArtifactPath = '$(Pipeline.Workspace)\PSPackagesOfficial' - $vstsCommandString = "vso[task.setvariable variable=PackageArtifactPath]$packageArtifactPath" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - displayName: 'Set package artifact variables' - - - download: PSPackagesOfficial - artifact: $(PackageArtifactName) - displayName: Download package - - - pwsh: 'Get-ChildItem $(PackageArtifactPath)\* -recurse | Select-Object -ExpandProperty Name' - displayName: 'Capture Artifact Listing' - - - pwsh: | - $message = @() - $packages = Get-ChildItem $(PackageArtifactPath)\* -recurse -include *.zip, *.msi - - if($packages.count -eq 0) {throw "No packages found in $(PackageArtifactPath)"} - - $packages | ForEach-Object { - if($_.Name -notmatch 'PowerShell-\d+\.\d+\.\d+\-([a-z]*.\d+\-)?win\-(fxdependent|x64|arm64|x86|fxdependentWinDesktop)\.(msi|zip){1}') - { - $messageInstance = "$($_.Name) is not a valid package name" - $message += $messageInstance - Write-Warning $messageInstance - } - } - - if($message.count -gt 0){throw ($message | out-string)} - displayName: 'Validate Zip and MSI Package Names' - - - pwsh: | - Get-ChildItem $(PackageArtifactPath)\* -recurse -include *.zip | ForEach-Object { - if($_.Name -match 'PowerShell-\d+\.\d+\.\d+\-([a-z]*.\d+\-)?win\-(${{ parameters.architecture }})\.(zip){1}') - { - Expand-Archive -Path $_.FullName -DestinationPath $(ob_outputDirectory) - } - } - displayName: 'Extract Zip to ob_outputDirectory' - - - pwsh: | - Write-Verbose "VPack Version: $(ob_createvpack_version)" -Verbose - Get-ChildItem -Path $(ob_outputDirectory)\* -Recurse - Get-Content $(ob_outputdirectory)\preview.json -ErrorAction SilentlyContinue | Write-Host - displayName: Debug Output Directory and Version - condition: succeededOrFailed() - - - pwsh: | - Write-Host "Using VPackPublishOverride variable" - $vpackVersion = '${{ parameters.VPackPublishOverride }}' - $vstsCommandString = "vso[task.setvariable variable=ob_createvpack_version]$vpackVersion" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - condition: ne('${{ parameters.VPackPublishOverride }}', ' ') - displayName: 'Set ob_createvpack_version with VPackPublishOverride' - - - pwsh: | - Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose - displayName: Capture Environment - condition: succeededOrFailed() - - - pwsh: | - Write-Verbose "VPack Version: $(ob_createvpack_version)" -Verbose - $vpackFiles = Get-ChildItem -Path $(ob_outputDirectory)\* -Recurse - if($vpackFiles.Count -eq 0) { - throw "No files found in $(ob_outputDirectory)" - } - $vpackFiles - displayName: Debug Output Directory and Version - condition: succeededOrFailed() - - - task: onebranch.pipeline.signing@1 - displayName: 'Onebranch Signing' - inputs: - command: 'sign' - signing_environment: 'azure-ado' - cp_code: $(windows_build_tools_cert_id) - files_to_sign: '**/*.exe;**/System.Management.Automation.dll' - search_root: $(ob_outputDirectory) + - template: templates/stages/PowerShell-vPack-Stages.yml + parameters: + createVPack: ${{ parameters.createVPack }} + vPackName: ${{ parameters.vPackName }} diff --git a/.pipelines/apiscan-gen-notice.yml b/.pipelines/apiscan-gen-notice.yml index 7596f79998e..df5ebaac091 100644 --- a/.pipelines/apiscan-gen-notice.yml +++ b/.pipelines/apiscan-gen-notice.yml @@ -8,9 +8,6 @@ parameters: displayName: Debugging - Enable CodeQL and set cadence to 1 hour type: boolean default: false - - name: SkipVerifyPackages - type: boolean - default: false variables: # PAT permissions NOTE: Declare a SymbolServerPAT variable in this group with a 'microsoft' organizanization scoped PAT with 'Symbols' Read permission. @@ -26,7 +23,7 @@ variables: - group: 'ComponentGovernance' - group: 'PoolNames' - name: LinuxContainerImage - value: onebranch.azurecr.io/linux/ubuntu-2004:latest + value: mcr.microsoft.com/onebranch/azurelinux/build:3.0 - name: WindowsContainerImage value: onebranch.azurecr.io/windows/ltsc2022/vse2022:latest - ${{ if eq(parameters['FORCE_CODEQL'],'true') }}: @@ -83,11 +80,11 @@ extends: break: true # always break the build on binskim issues in addition to TSA upload policheck: break: true # always break the build on policheck issues. You can disable it by setting to 'false' - # APIScan requires a non-Ready-To-Run build + # APIScan requires a non-Ready-To-Run build apiscan: enabled: true softwareName: "PowerShell" # Default is repo name - versionNumber: "7.5" # Default is build number + versionNumber: "7.6" # Default is build number isLargeApp: false # Default: false. symbolsFolder: $(SymbolsServerUrl);$(ob_outputDirectory) #softwareFolder - relative path to a folder to be scanned. Default value is root of artifacts folder @@ -112,4 +109,3 @@ extends: - template: /.pipelines/templates/compliance/generateNotice.yml@self parameters: parentJobs: [] - SkipVerifyPackages: ${{ parameters.SkipVerifyPackages }} diff --git a/.pipelines/store/PDP/PDP-Media/en-US/.gitkeep b/.pipelines/store/PDP/PDP-Media/en-US/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/.pipelines/store/PDP/PDP/en-US/PDP.xml b/.pipelines/store/PDP/PDP/en-US/PDP.xml new file mode 100644 index 00000000000..ce36a3677f7 --- /dev/null +++ b/.pipelines/store/PDP/PDP/en-US/PDP.xml @@ -0,0 +1,151 @@ + + + + + + + + + + + + + Shell + + PowerShell + + Terminal + + Command Line + + Automation + + Task Automation + + Scripting + + + PowerShell is a task-based command-line shell and scripting language built on .NET. PowerShell helps system administrators and power-users rapidly automate task that manage operating systems (Linux, macOS, and Windows) and processes. + +PowerShell commands let you manage computers from the command line. PowerShell providers let you access data stores, such as the registry and certificate store, as easily as you access the file system. PowerShell includes a rich expression parser and a fully developed scripting language. + +PowerShell is Open Source. See https://github.com/powershell/powershell + + + + + + + + + + + + + + + + + + + + + + Please see our GitHub releases page for additional details. + + + + + + + + + + + + + + + + + + + + Interactive Shell + + Scripting Language + + Remote Management + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Microsoft Corporation + + + + + https://github.com/PowerShell/PowerShell + + https://github.com/PowerShell/PowerShell/issues + + https://go.microsoft.com/fwlink/?LinkID=521839 + diff --git a/.pipelines/store/SBConfig.json b/.pipelines/store/SBConfig.json new file mode 100644 index 00000000000..a52d60b045f --- /dev/null +++ b/.pipelines/store/SBConfig.json @@ -0,0 +1,69 @@ +{ + "helpUri": "https:\\\\aka.ms\\StoreBroker_Config", + "schemaVersion": 2, + "packageParameters": { + "PDPRootPath": "", + "Release": "", + "PDPInclude": [ + "PDP.xml" + ], + "PDPExclude": [], + "LanguageExclude": [ + "default", + "qps-ploc", + "qps-ploca", + "qps-plocm" + ], + "MediaRootPath": "", + "MediaFallbackLanguage": "en-US", + "PackagePath": [], + "OutPath": "", + "OutName": "", + "DisableAutoPackageNameFormatting": false + }, + "appSubmission": { + "productId": "", + "targetPublishMode": "Immediate", + "targetPublishDate": null, + "visibility": "NotSet", + "pricing": { + "priceId": "NotAvailable", + "trialPeriod": "NoFreeTrial", + "marketSpecificPricings": {}, + "sales": [] + }, + "allowTargetFutureDeviceFamilies": { + "Xbox": false, + "Team": false, + "Holographic": false, + "Desktop": false, + "Mobile": false + }, + "allowMicrosoftDecideAppAvailabilityToFutureDeviceFamilies": false, + "enterpriseLicensing": "None", + "applicationCategory": "NotSet", + "hardwarePreferences": [], + "hasExternalInAppProducts": false, + "meetAccessibilityGuidelines": false, + "canInstallOnRemovableMedia": false, + "automaticBackupEnabled": false, + "isGameDvrEnabled": false, + "gamingOptions": [ + { + "genres": [], + "isLocalMultiplayer": false, + "isLocalCooperative": false, + "isOnlineMultiplayer": false, + "isOnlineCooperative": false, + "localMultiplayerMinPlayers": 0, + "localMultiplayerMaxPlayers": 0, + "localCooperativeMinPlayers": 0, + "localCooperativeMaxPlayers": 0, + "isBroadcastingPrivilegeGranted": false, + "isCrossPlayEnabled": false, + "kinectDataForExternal": "Disabled" + } + ], + "notesForCertification": "" + } +} diff --git a/.pipelines/templates/SetVersionVariables.yml b/.pipelines/templates/SetVersionVariables.yml index 9f692373f6c..30ed1704022 100644 --- a/.pipelines/templates/SetVersionVariables.yml +++ b/.pipelines/templates/SetVersionVariables.yml @@ -1,49 +1,18 @@ parameters: - ReleaseTagVar: v6.2.0 - ReleaseTagVarName: ReleaseTagVar - CreateJson: 'no' - UseJson: 'yes' +- name: ReleaseTagVar + default: v6.2.0 +- name: ReleaseTagVarName + default: ReleaseTagVar +- name: CreateJson + default: 'no' +- name: ob_restore_phase + type: boolean + default: true steps: -- ${{ if eq(parameters['UseJson'],'yes') }}: - - task: DownloadBuildArtifacts@0 - inputs: - artifactName: 'drop_prep_SetVars' - itemPattern: '*.json' - downloadPath: '$(System.ArtifactsDirectory)' - displayName: Download Build Info Json - env: - ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue - -- powershell: | - $path = "./build.psm1" - if($env:REPOROOT){ - Write-Verbose "reporoot already set to ${env:REPOROOT}" -Verbose - exit 0 - } - if(Test-Path -Path $path) - { - Write-Verbose "reporoot detected at: ." -Verbose - $repoRoot = '.' - } - else{ - $path = "./PowerShell/build.psm1" - if(Test-Path -Path $path) - { - Write-Verbose "reporoot detect at: ./PowerShell" -Verbose - $repoRoot = './PowerShell' - } - } - if($repoRoot) { - $vstsCommandString = "vso[task.setvariable variable=repoRoot]$repoRoot" - Write-Host ("sending " + $vstsCommandString) - Write-Host "##$vstsCommandString" - } else { - Write-Verbose -Verbose "repo not found" - } - displayName: 'Set repo Root' - env: - ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue +- template: set-reporoot.yml@self + parameters: + ob_restore_phase: ${{ parameters.ob_restore_phase }} - powershell: | $createJson = ("${{ parameters.CreateJson }}" -ne "no") @@ -69,11 +38,11 @@ steps: Write-Host "##$vstsCommandString" displayName: 'Set ${{ parameters.ReleaseTagVarName }} and other version Variables' env: - ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue + ob_restore_phase: ${{ parameters.ob_restore_phase }} - powershell: | Get-ChildItem -Path Env: | Out-String -Width 150 displayName: Capture environment condition: succeededOrFailed() env: - ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue + ob_restore_phase: ${{ parameters.ob_restore_phase }} diff --git a/.pipelines/templates/channelSelection.yml b/.pipelines/templates/channelSelection.yml new file mode 100644 index 00000000000..d6ddb53256e --- /dev/null +++ b/.pipelines/templates/channelSelection.yml @@ -0,0 +1,49 @@ +steps: +- pwsh: | + # Determine LTS, Preview, or Stable + $metadata = Get-Content "$(Build.SourcesDirectory)/PowerShell/tools/metadata.json" -Raw | ConvertFrom-Json + + $LTS = $metadata.LTSRelease.PublishToChannels + $Stable = $metadata.StableRelease.PublishToChannels + $isPreview = '$(OutputReleaseTag.releaseTag)' -match '-' + $releaseTag = '$(OutputReleaseTag.releaseTag)' + + # Rebuild branches should be treated as preview builds + # NOTE: The following regex is duplicated from rebuild-branch-check.yml. + # This duplication is necessary because channelSelection.yml does not call rebuild-branch-check.yml, + # and is used in contexts where that check may not have run. + # If you update this regex, also update it in rebuild-branch-check.yml to keep them in sync. + $isRebuildBranch = '$(Build.SourceBranch)' -match 'refs/heads/rebuild/.*-rebuild\.' + + # If this is a rebuild branch, force preview mode and ignore LTS metadata + if ($isRebuildBranch) { + $IsLTS = $false + $IsStable = $false + $IsPreview = $true + Write-Verbose -Message "Rebuild branch detected, forcing Preview channel" -Verbose + } + else { + $IsLTS = [bool]$LTS + $IsStable = [bool]$Stable + $IsPreview = [bool]$isPreview + } + + $channelVars = @{ + IsLTS = $IsLTS + IsStable = $IsStable + IsPreview = $IsPreview + } + + $trueCount = ($channelVars.Values | Where-Object { $_ }) | Measure-Object | Select-Object -ExpandProperty Count + if ($trueCount -gt 1) { + Write-Error "Only one of IsLTS, IsStable, or IsPreview can be true. Current values: IsLTS=$IsLTS, IsStable=$IsStable, IsPreview=$IsPreview" + exit 1 + } + + foreach ($name in $channelVars.Keys) { + $value = if ($channelVars[$name]) { 'true' } else { 'false' } + Write-Verbose -Message "Setting $name variable: $value" -Verbose + Write-Host "##vso[task.setvariable variable=$name;isOutput=true]$value" + } + name: ChannelSelection + displayName: Select Preview, Stable, or LTS Channel diff --git a/.pipelines/templates/checkAzureContainer.yml b/.pipelines/templates/checkAzureContainer.yml index a6a86214d07..3e383d2c572 100644 --- a/.pipelines/templates/checkAzureContainer.yml +++ b/.pipelines/templates/checkAzureContainer.yml @@ -3,6 +3,8 @@ jobs: variables: - group: Azure Blob variable group - group: AzureBlobServiceConnection + - name: ob_artifactBaseName + value: BuildInfoJson - name: ob_outputDirectory value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT/BuildJson' - name: ob_sdl_sbom_enabled @@ -29,7 +31,6 @@ jobs: parameters: ReleaseTagVar: $(ReleaseTagVar) CreateJson: yes - UseJson: no - template: /.pipelines/templates/cloneToOfficialPath.yml@self diff --git a/.pipelines/templates/cloneToOfficialPath.yml b/.pipelines/templates/cloneToOfficialPath.yml index 844d8b8028d..b060c713683 100644 --- a/.pipelines/templates/cloneToOfficialPath.yml +++ b/.pipelines/templates/cloneToOfficialPath.yml @@ -1,5 +1,9 @@ parameters: - nativePathRoot: '' +- name: nativePathRoot + default: '' +- name: ob_restore_phase + type: boolean + default: true steps: - powershell: | @@ -12,8 +16,16 @@ steps: else { Write-Verbose -Verbose -Message "No cleanup required." } - git clone --quiet $env:REPOROOT $nativePath + # REPOROOT must be set by the pipeline - this is where the repository was checked out + $sourceDir = $env:REPOROOT + if (-not $sourceDir) { throw "REPOROOT environment variable is not set. This step depends on REPOROOT being configured in the pipeline." } + + $buildModulePath = Join-Path $sourceDir "build.psm1" + if (-not (Test-Path $buildModulePath)) { throw "build.psm1 not found at: $buildModulePath. REPOROOT must point to the PowerShell repository root." } + + Write-Verbose -Verbose -Message "Cloning from: $sourceDir to $nativePath" + git clone --quiet $sourceDir $nativePath displayName: Clone PowerShell Repo to /PowerShell errorActionPreference: silentlycontinue env: - ob_restore_phase: true # This ensures checkout is done at the beginning of the restore phase + ob_restore_phase: ${{ parameters.ob_restore_phase }} diff --git a/.pipelines/templates/compliance/apiscan.yml b/.pipelines/templates/compliance/apiscan.yml index 17f07a597b5..b5a15699026 100644 --- a/.pipelines/templates/compliance/apiscan.yml +++ b/.pipelines/templates/compliance/apiscan.yml @@ -4,8 +4,6 @@ jobs: - job: APIScan variables: - - name: runCodesignValidationInjection - value : false - name: NugetSecurityAnalysisWarningLevel value: none - name: ReleaseTagVar @@ -17,7 +15,6 @@ jobs: - name: branchCounter value: $[counter(variables['branchCounterKey'], 1)] - group: DotNetPrivateBuildAccess - - group: Azure Blob variable group - group: ReleasePipelineSecrets - group: mscodehub-feed-read-general - group: mscodehub-feed-read-akv @@ -51,8 +48,7 @@ jobs: - template: ../SetVersionVariables.yml parameters: ReleaseTagVar: $(ReleaseTagVar) - CreateJson: yes - UseJson: no + CreateJson: no - template: ../insert-nuget-config-azfeed.yml parameters: @@ -75,34 +71,6 @@ jobs: workingDirectory: '$(repoRoot)' retryCountOnTaskFailure: 2 - - task: AzurePowerShell@5 - displayName: Download winverify-private Artifacts - inputs: - azureSubscription: az-blob-cicd-infra - scriptType: inlineScript - azurePowerShellVersion: LatestVersion - workingDirectory: '$(repoRoot)' - pwsh: true - inline: | - # download smybols for getfilesiginforedist.dll - $downloadsDirectory = '$(Build.ArtifactStagingDirectory)/downloads' - $uploadedDirectory = '$(Build.ArtifactStagingDirectory)/uploaded' - $storageAccountName = "pscoretestdata" - $containerName = 'winverify-private' - $winverifySymbolsPath = New-Item -ItemType Directory -Path '$(System.ArtifactsDirectory)/winverify-symbols' -Force - $dllName = 'getfilesiginforedist.dll' - $winverifySymbolsDllPath = Join-Path $winverifySymbolsPath $dllName - - $context = New-AzStorageContext -StorageAccountName $storageAccountName -UseConnectedAccount - - Get-AzStorageBlobContent -Container $containerName -Blob $dllName -Destination $winverifySymbolsDllPath -Context $context - - - pwsh: | - Get-ChildItem -Path '$(System.ArtifactsDirectory)/winverify-symbols' - displayName: Capture winverify-private Artifacts - workingDirectory: '$(repoRoot)' - condition: succeededOrFailed() - - task: CodeQL3000Init@0 # Add CodeQL Init task right before your 'Build' step. displayName: 🔏 CodeQL 3000 Init condition: eq(variables['CODEQL_ENABLED'], 'true') @@ -121,23 +89,35 @@ jobs: Remove-Item -Recurse -Force $OutputFolder/ref } - Copy-Item -Path "$OutputFolder\*" -Destination '$(ob_outputDirectory)' -Recurse -Verbose + $Destination = '$(ob_outputDirectory)' + if (-not (Test-Path $Destination)) { + Write-Verbose -Verbose -Message "Creating destination folder '$Destination'" + $null = mkdir $Destination + } + + Copy-Item -Path "$OutputFolder\*" -Destination $Destination -Recurse -Verbose workingDirectory: '$(repoRoot)' displayName: 'Build PowerShell Source' - pwsh: | - # Only key windows runtimes - Get-ChildItem -Path '$(ob_outputDirectory)\runtimes\*' -File -Recurse | Where-Object {$_.FullName -notmatch '.*\/runtimes\/win'} | Foreach-Object { + # Only keep windows runtimes + Write-Verbose -Verbose -Message "Deleting non-win-x64 runtimes ..." + Get-ChildItem -Path '$(ob_outputDirectory)\runtimes\*' | Where-Object {$_.FullName -notmatch '.*\\runtimes\\win'} | Foreach-Object { Write-Verbose -Verbose -Message "Deleting $($_.FullName)" - Remove-Item -Force -Verbose -Path $_.FullName + Remove-Item -Path $_.FullName -Recurse -Force } - # Temporarily remove runtimes/win-x64 due to issues with that runtime - Get-ChildItem -Path '$(ob_outputDirectory)\runtimes\*' -File -Recurse | Where-Object {$_.FullName -match '.*\/runtimes\/win-x86\/'} | Foreach-Object { + # Remove win-x86/arm/arm64 runtimes due to issues with those runtimes + Write-Verbose -Verbose -Message "Temporarily deleting win-x86/arm/arm64 runtimes ..." + Get-ChildItem -Path '$(ob_outputDirectory)\runtimes\*' | Where-Object {$_.FullName -match '.*\\runtimes\\win-(x86|arm)'} | Foreach-Object { Write-Verbose -Verbose -Message "Deleting $($_.FullName)" - Remove-Item -Force -Verbose -Path $_.FullName + Remove-Item -Path $_.FullName -Recurse -Force } + Write-Host + Write-Verbose -Verbose -Message "Show content in 'runtimes' folder:" + Get-ChildItem -Path '$(ob_outputDirectory)\runtimes' + Write-Host workingDirectory: '$(repoRoot)' displayName: 'Remove unused runtimes' diff --git a/.pipelines/templates/compliance/generateNotice.yml b/.pipelines/templates/compliance/generateNotice.yml index 7de316e8b49..aec44b9b8f6 100644 --- a/.pipelines/templates/compliance/generateNotice.yml +++ b/.pipelines/templates/compliance/generateNotice.yml @@ -4,14 +4,10 @@ parameters: - name: parentJobs type: jobList - - name: SkipVerifyPackages - type: boolean jobs: - job: generateNotice variables: - - name: runCodesignValidationInjection - value : false - name: NugetSecurityAnalysisWarningLevel value: none - name: ob_outputDirectory @@ -57,12 +53,7 @@ jobs: - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 displayName: 'Component Detection' inputs: - sourceScanPath: '$(repoRoot)\tools' - - - pwsh: | - $(repoRoot)/tools/clearlyDefined/ClearlyDefined.ps1 -TestAndHarvest - displayName: Verify that packages have license data - condition: eq(${{ parameters.SkipVerifyPackages }}, false) + sourceScanPath: '$(repoRoot)\tools\cgmanifest\tpn' - task: msospo.ospo-extension.8d7f9abb-6896-461d-9e25-4f74ed65ddb2.notice@0 displayName: 'NOTICE File Generator' diff --git a/.pipelines/templates/create-msixbundle-vpack.yml b/.pipelines/templates/create-msixbundle-vpack.yml new file mode 100644 index 00000000000..df46523675f --- /dev/null +++ b/.pipelines/templates/create-msixbundle-vpack.yml @@ -0,0 +1,178 @@ +parameters: + - name: Channel + type: string + - name: createVPack + type: boolean + +jobs: +- job: Bundle_${{ parameters.Channel }} + condition: contains(variables['EnabledChannels'], '${{ parameters.Channel }}') + pool: + type: windows + + variables: + ArtifactPlatform: 'windows' + Channel: ${{ parameters.Channel }} + ob_outputDirectory: '$(BUILD.SOURCESDIRECTORY)\out' + ob_artifactBaseName: 'drop_pack_$(Channel)' + ob_createvpack_enabled: ${{ parameters.createVPack }} + ob_createvpack_packagename: 'PowerShell7-$(Channel).Store.app' + ob_createvpack_owneralias: 'dongbow' + ob_createvpack_description: 'VPack for the PowerShell 7 Store Application ($(Channel))' + ob_createvpack_targetDestinationDirectory: '$(Destination)' ## The value is from the 'CreateVpack' task, used when pulling the generated VPack. + ob_createvpack_propsFile: false + ob_createvpack_provData: true + ob_createvpack_metadata: '$(Build.SourceVersion)' + ob_createvpack_versionAs: string + ob_createvpack_version: '$(Version)' + ob_createvpack_verbose: true + + steps: + - checkout: self + displayName: Checkout source code - during restore + clean: true + path: s ## $(Build.SourcesDirectory) is at '$(Pipeline.Workspace)\s', so we need to check out repo to the 's' folder. + env: + ob_restore_phase: true + + - template: /.pipelines/templates/SetVersionVariables.yml@self + parameters: + ReleaseTagVar: $(ReleaseTagVar) + CreateJson: no + + - template: /.pipelines/templates/shouldSign.yml@self + + - task: DownloadPipelineArtifact@2 + inputs: + artifactName: drop_build_x64 + itemPattern: | + **/*.msix + targetPath: '$(Build.ArtifactStagingDirectory)\downloads' + displayName: Download msix for x64 + + - task: DownloadPipelineArtifact@2 + inputs: + artifactName: drop_build_arm64 + itemPattern: | + **/*.msix + targetPath: '$(Build.ArtifactStagingDirectory)\downloads' + displayName: Download msix for arm64 + + # Finds the makeappx tool on the machine. + - pwsh: | + Write-Verbose -Verbose 'PowerShell Version: $(Version)' + $cmd = Get-Command makeappx.exe -ErrorAction Ignore + if ($cmd) { + Write-Verbose -Verbose 'makeappx available in PATH' + $exePath = $cmd.Source + } else { + $makeappx = Get-ChildItem -Recurse 'C:\Program Files (x86)\Windows Kits\10\makeappx.exe' | + Where-Object { $_.DirectoryName -match 'x64' } | + Select-Object -Last 1 + $exePath = $makeappx.FullName + Write-Verbose -Verbose "makeappx was found: $exePath" + } + $vstsCommandString = "vso[task.setvariable variable=MakeAppxPath]$exePath" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + displayName: Find makeappx tool + retryCountOnTaskFailure: 1 + + - pwsh: | + $sourceDir = '$(Pipeline.Workspace)\releasePipeline\msix' + $null = New-Item -Path $sourceDir -ItemType Directory -Force + + $channel = '$(Channel)' + if ($channel -eq 'LTS') { + Write-Verbose -Verbose "LTS channel. Remove Stable MSIX packages" + $stablePkgs = Get-ChildItem -Path "$(Build.ArtifactStagingDirectory)\downloads\*.msix" -Recurse | + Where-Object { $_.FullName -notlike '*-LTS-*.msix' } | ForEach-Object FullName + + if ($stablePkgs) { + Remove-Item -Path $stablePkgs -Force -Verbose -ErrorAction Stop + } else { + Write-Verbose -Verbose "No Stable MSIX package was found." + } + } + else { + Write-Verbose -Verbose "Stable channel. Remove LTS MSIX packages" + $ltsPkgs = Get-ChildItem -Path "$(Build.ArtifactStagingDirectory)\downloads\*.msix" -Recurse | + Where-Object { $_.FullName -like '*-LTS-*.msix' } | ForEach-Object FullName + + if ($ltsPkgs) { + Remove-Item -Path $ltsPkgs -Force -Verbose -ErrorAction Stop + } else { + Write-Verbose -Verbose "No LTS MSIX package was found." + } + } + + $msixFiles = Get-ChildItem -Path "$(Build.ArtifactStagingDirectory)\downloads\*.msix" -Recurse + foreach ($msixFile in $msixFiles) { + $null = Copy-Item -Path $msixFile.FullName -Destination $sourceDir -Force -Verbose + } + + $file = Get-ChildItem $sourceDir | Select-Object -First 1 + $prefix = ($file.BaseName -split "-win")[0] + $pkgName = "$prefix.msixbundle" + Write-Verbose -Verbose "Creating $pkgName" + + $makeappx = '$(MakeAppxPath)' + $outputDir = "$sourceDir\output" + New-Item $outputDir -Type Directory -Force > $null + & $makeappx bundle /d $sourceDir /p "$outputDir\$pkgName" + if ($LASTEXITCODE -ne 0) { + throw "makeappx bundle failed with exit code $LASTEXITCODE" + } + + Get-ChildItem -Path $sourceDir -Recurse | Out-String -Width 200 + $vstsCommandString = "vso[task.setvariable variable=BundleDir]$outputDir" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + displayName: Create MsixBundle + retryCountOnTaskFailure: 1 + + - task: onebranch.pipeline.signing@1 + displayName: Sign MsixBundle + inputs: + command: 'sign' + signing_profile: $(MSIXProfile) + files_to_sign: '**/*.msixbundle' + search_root: '$(BundleDir)' + + - pwsh: | + $signedBundle = Get-ChildItem -Path $(BundleDir) -Filter "*.msixbundle" -File + Write-Verbose -Verbose "Signed bundle: $signedBundle" + + $signature = Get-AuthenticodeSignature -FilePath $signedBundle.FullName + if ($signature.Status -ne 'Valid') { + throw "The bundle file doesn't have a valid signature. Signature status: $($signature.Status)" + } + + if (-not (Test-Path '$(ob_outputDirectory)' -PathType Container)) { + $null = New-Item '$(ob_outputDirectory)' -ItemType Directory -ErrorAction Stop + } + + $channel = '$(Channel)' + $targetFileName = if ($channel -eq 'LTS') { + 'Microsoft.PowerShell-LTS_8wekyb3d8bbwe.msixbundle' + } else { + 'Microsoft.PowerShell_8wekyb3d8bbwe.msixbundle' + } + $targetPath = Join-Path '$(ob_outputDirectory)' $targetFileName + Copy-Item -Verbose -Path $signedBundle.FullName -Destination $targetPath + + Write-Verbose -Verbose "Uploaded Bundle:" + Get-ChildItem -Path $(ob_outputDirectory) | Out-String -Width 200 -Stream | Write-Verbose -Verbose + displayName: 'Stage msixbundle for VPack' + + - pwsh: | + Write-Verbose "VPack enabled: $(ob_createvpack_enabled)" -Verbose + Write-Verbose "VPack Name: $(ob_createvpack_packagename)" -Verbose + Write-Verbose "VPack Version: $(ob_createvpack_version)" -Verbose + + $vpackFiles = Get-ChildItem -Path '$(ob_outputDirectory)\*' -Recurse + if($vpackFiles.Count -eq 0) { + throw "No files found in $(ob_outputDirectory)" + } + $vpackFiles | Out-String -Width 200 + displayName: Debug Output Directory and Version diff --git a/.pipelines/templates/install-dotnet.yml b/.pipelines/templates/install-dotnet.yml new file mode 100644 index 00000000000..464e13d1047 --- /dev/null +++ b/.pipelines/templates/install-dotnet.yml @@ -0,0 +1,24 @@ +parameters: +- name: ob_restore_phase + type: boolean + default: true + +steps: + - pwsh: | + if (-not (Test-Path '$(RepoRoot)')) { + $psRoot = '$(Build.SourcesDirectory)/PowerShell' + Set-Location $psRoot -Verbose + } + + $version = Get-Content ./global.json | ConvertFrom-Json | Select-Object -ExpandProperty sdk | Select-Object -ExpandProperty version + + Write-Verbose -Verbose "Installing .NET SDK with version $version" + + Import-Module ./build.psm1 -Force + Install-Dotnet -Version $version -Verbose + + displayName: 'Install dotnet SDK' + workingDirectory: $(RepoRoot) + env: + ob_restore_phase: ${{ parameters.ob_restore_phase }} + diff --git a/.pipelines/templates/linux-package-build.yml b/.pipelines/templates/linux-package-build.yml index 95996a597fe..e37d8555533 100644 --- a/.pipelines/templates/linux-package-build.yml +++ b/.pipelines/templates/linux-package-build.yml @@ -1,9 +1,8 @@ parameters: unsignedDrop: 'drop_linux_build_linux_x64' - signedeDrop: 'drop_linux_sign_linux_x64' + signedDrop: 'drop_linux_sign_linux_x64' packageType: deb jobName: 'deb' - signingProfile: 'CP-450779-pgpdetached' jobs: - job: ${{ parameters.jobName }} @@ -13,8 +12,6 @@ jobs: type: linux variables: - - name: runCodesignValidationInjection - value: false - name: nugetMultiFeedWarnLevel value: none - name: NugetSecurityAnalysisWarningLevel @@ -22,6 +19,7 @@ jobs: - name: skipNugetSecurityAnalysis value: true - group: DotNetPrivateBuildAccess + - group: certificate_logical_to_actual - name: ob_outputDirectory value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' - name: ob_sdl_binskim_enabled @@ -36,8 +34,16 @@ jobs: value: $(Build.SourcesDirectory)/PowerShell/.config/tsaoptions.json - name: ob_sdl_credscan_suppressionsFile value: $(Build.SourcesDirectory)/PowerShell/.config/suppress.json - - name: SigningProfile - value: ${{ parameters.signingProfile }} + # PGP signing profile selection: Mariner (Azure Linux) packages ship through + # a different distribution channel and must be signed with the Mariner release + # key; all other Linux packages use the standard PowerShell Linux key. Both + # key codes come from the `certificate_logical_to_actual` variable group. + - ${{ if startsWith(parameters.jobName, 'mariner') }}: + - name: SigningProfile + value: $(pgp_release_cert_id) + - ${{ else }}: + - name: SigningProfile + value: $(pgp_linux_cert_id) steps: - checkout: self @@ -54,8 +60,7 @@ jobs: - template: SetVersionVariables.yml@self parameters: ReleaseTagVar: $(ReleaseTagVar) - CreateJson: yes - UseJson: no + CreateJson: no - template: shouldSign.yml @@ -63,6 +68,8 @@ jobs: parameters: nativePathRoot: '$(Agent.TempDirectory)' + - template: rebuild-branch-check.yml@self + - download: CoOrdinatedBuildPipeline artifact: ${{ parameters.unsignedDrop }} displayName: 'Download unsigned artifacts' @@ -87,6 +94,8 @@ jobs: env: ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue + - template: /.pipelines/templates/install-dotnet.yml@self + - pwsh: | $packageType = '$(PackageType)' Write-Verbose -Verbose "packageType = $packageType" @@ -103,7 +112,7 @@ jobs: Import-Module "$repoRoot/build.psm1" Import-Module "$repoRoot/tools/packaging" - Start-PSBootstrap -Scenario Package + Start-PSBootstrap -Scenario Both $psOptionsPath = "$(Pipeline.Workspace)/CoOrdinatedBuildPipeline/${unsignedDrop}/psoptions/psoptions.json" @@ -123,6 +132,7 @@ jobs: 'tar' { 'Signed-linux-x64', 'powershell*.tar.gz' } 'tar-alpine-fxdependent' { 'Signed-fxdependent-noopt-linux-musl-x64', 'powershell*.tar.gz' } 'deb' { 'Signed-linux-x64', 'powershell*.deb' } + 'deb-arm64' { 'Signed-linux-arm64', 'powershell*.deb' } 'rpm-fxdependent' { 'Signed-fxdependent-linux-x64', 'powershell*.rpm' } 'rpm-fxdependent-arm64' { 'Signed-fxdependent-linux-arm64', 'powershell*.rpm' } 'rpm' { 'Signed-linux-x64', 'powershell*.rpm' } @@ -138,12 +148,22 @@ jobs: } $metadata = Get-Content "$repoRoot/tools/metadata.json" -Raw | ConvertFrom-Json - $LTS = $metadata.LTSRelease.Package - if ($LTS) { - Write-Verbose -Message "LTS Release: $LTS" + Write-Verbose -Verbose "metadata:" + $metadata | Out-String | Write-Verbose -Verbose + + # Use the rebuild branch check from the template + $isRebuildBranch = '$(RebuildBranchCheck.IsRebuildBranch)' -eq 'true' + + # Don't build LTS packages for rebuild branches + $LTS = $metadata.LTSRelease.Package -and -not $isRebuildBranch + + if ($isRebuildBranch) { + Write-Verbose -Message "Rebuild branch detected, skipping LTS package build" -Verbose } + Write-Verbose -Verbose "LTS: $LTS" + if (-not (Test-Path $(ob_outputDirectory))) { New-Item -ItemType Directory -Path $(ob_outputDirectory) -Force } @@ -153,6 +173,11 @@ jobs: Start-PSPackage -Type $packageType -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath + if ($LTS) { + Write-Verbose -Message "LTS Release: $LTS" -Verbose + Start-PSPackage -Type $packageType -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath -LTS + } + $vstsCommandString = "vso[task.setvariable variable=PackageFilter]$pkgFilter" Write-Host ("sending " + $vstsCommandString) Write-Host "##$vstsCommandString" @@ -176,6 +201,13 @@ jobs: $pkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $pkgFilter -Recurse -File | Select-Object -ExpandProperty FullName Write-Verbose -Verbose "pkgPath: $pkgPath" Copy-Item -Path $pkgPath -Destination '$(ob_outputDirectory)' -Force -Verbose + + if ($pkgPath -like '*.tar.gz') { + $entry = & tar -tzvf $pkgPath | Where-Object { $_ -match '\spwsh$' } | Select-Object -First 1 + if ($entry -notmatch '^-..x') { + throw "pwsh is not executable in $pkgPath : $entry" + } + } displayName: 'Copy artifacts to output directory' env: __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) diff --git a/.pipelines/templates/linux.yml b/.pipelines/templates/linux.yml index 398e8fe5fef..97b594830b3 100644 --- a/.pipelines/templates/linux.yml +++ b/.pipelines/templates/linux.yml @@ -10,11 +10,9 @@ jobs: pool: type: linux variables: - - name: runCodesignValidationInjection - value: false - name: NugetSecurityAnalysisWarningLevel value: none - - name: DOTNET_SKIP_FIRST_TIME_EXPERIENCE + - name: DOTNET_NOLOGO value: 1 - group: DotNetPrivateBuildAccess - name: ob_outputDirectory @@ -64,12 +62,7 @@ jobs: AnalyzeInPipeline: false Language: csharp - - task: UseDotNet@2 - inputs: - useGlobalJson: true - workingDirectory: $(PowerShellRoot) - env: - ob_restore_phase: true + - template: /.pipelines/templates/install-dotnet.yml@self - pwsh: | $runtime = $env:RUNTIME @@ -144,11 +137,9 @@ jobs: pool: type: windows variables: - - name: runCodesignValidationInjection - value: false - name: NugetSecurityAnalysisWarningLevel value: none - - name: DOTNET_SKIP_FIRST_TIME_EXPERIENCE + - name: DOTNET_NOLOGO value: 1 - group: DotNetPrivateBuildAccess - group: certificate_logical_to_actual @@ -201,5 +192,6 @@ jobs: - template: /.pipelines/templates/obp-file-signing.yml@self parameters: binPath: $(DropRootPath) + OfficialBuild: $(ps_official_build) - template: /.pipelines/templates/step/finalize.yml@self diff --git a/.pipelines/templates/mac-package-build.yml b/.pipelines/templates/mac-package-build.yml index 669ab3c8437..89dab90aa04 100644 --- a/.pipelines/templates/mac-package-build.yml +++ b/.pipelines/templates/mac-package-build.yml @@ -15,8 +15,6 @@ jobs: variables: - name: HOMEBREW_NO_ANALYTICS value: 1 - - name: runCodesignValidationInjection - value: false - name: nugetMultiFeedWarnLevel value: none - name: NugetSecurityAnalysisWarningLevel @@ -24,6 +22,7 @@ jobs: - name: skipNugetSecurityAnalysis value: true - group: DotNetPrivateBuildAccess + - group: certificate_logical_to_actual - name: ob_outputDirectory value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' - name: ob_sdl_binskim_enabled @@ -52,8 +51,7 @@ jobs: - template: SetVersionVariables.yml@self parameters: ReleaseTagVar: $(ReleaseTagVar) - CreateJson: yes - UseJson: no + CreateJson: no - template: shouldSign.yml @@ -61,6 +59,8 @@ jobs: parameters: nativePathRoot: '$(Agent.TempDirectory)' + - template: rebuild-branch-check.yml@self + - download: CoOrdinatedBuildPipeline artifact: macosBinResults-${{ parameters.buildArchitecture }} @@ -77,6 +77,14 @@ jobs: # Diagnostics is not critical it passes every time it runs continueOnError: true + - pwsh: | + $signedDir = "$(Pipeline.Workspace)/CoOrdinatedBuildPipeline/drop_macos_sign_${{ parameters.buildArchitecture }}/Signed-${{ parameters.buildArchitecture }}" + Get-ChildItem $signedDir -Recurse -Include 'pwsh', '*.dylib' | ForEach-Object { + codesign --verify --deep --strict --verbose=4 $_.FullName + if ($LASTEXITCODE -ne 0) { throw "codesign verification failed for $($_.FullName)" } + } + displayName: 'Verify Apple codesign on signed binaries' + - pwsh: | # Add -SkipReleaseChecks as a mitigation to unblock release. # macos-10.15 does not allow creating a folder under root. Hence, moving the folder. @@ -103,28 +111,72 @@ jobs: Restore-PSOptions -PSOptionsPath "$psoptionsPath" Get-PSOptions | Write-Verbose -Verbose + if (-not (Test-Path "$repoRoot/tools/metadata.json")) { + throw "metadata.json not found in $repoRoot/tools" + } + $metadata = Get-Content "$repoRoot/tools/metadata.json" -Raw | ConvertFrom-Json - $LTS = $metadata.LTSRelease.Package + + Write-Verbose -Verbose "metadata:" + $metadata | Out-String | Write-Verbose -Verbose + + # Use the rebuild branch check from the template + $isRebuildBranch = '$(RebuildBranchCheck.IsRebuildBranch)' -eq 'true' + + # Don't build LTS packages for rebuild branches + $LTS = $metadata.LTSRelease.Package -and -not $isRebuildBranch + + if ($isRebuildBranch) { + Write-Verbose -Message "Rebuild branch detected, skipping LTS package build" -Verbose + } + + Write-Verbose -Verbose "LTS: $LTS" if ($LTS) { - Write-Verbose -Message "LTS Release: $LTS" + Write-Verbose -Message "LTS Release: $LTS" -Verbose } Start-PSBootstrap -Scenario Package $macosRuntime = "osx-$buildArch" - Start-PSPackage -Type osxpkg -SkipReleaseChecks -MacOSRuntime $macosRuntime -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath -LTS:$LTS + Start-PSPackage -Type osxpkg -SkipReleaseChecks -MacOSRuntime $macosRuntime -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath + + if ($LTS) { + Start-PSPackage -Type osxpkg -SkipReleaseChecks -MacOSRuntime $macosRuntime -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath -LTS + } + $pkgNameFilter = "powershell-*$macosRuntime.pkg" - $pkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $pkgNameFilter -Recurse -File | Select-Object -ExpandProperty FullName - Write-Host "##vso[artifact.upload containerfolder=macos-pkgs;artifactname=macos-pkgs]$pkgPath" + Write-Verbose -Verbose "Looking for pkg packages with filter: $pkgNameFilter in '$(Pipeline.Workspace)' to upload..." + $pkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $pkgNameFilter -Recurse -File + + foreach($p in $pkgPath) { + $file = $p.FullName + Write-Verbose -verbose "Uploading $file to macos-pkgs" + Write-Host "##vso[artifact.upload containerfolder=macos-pkgs;artifactname=macos-pkgs]$file" + } Start-PSPackage -Type tar -SkipReleaseChecks -MacOSRuntime $macosRuntime -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath -LTS:$LTS $tarPkgNameFilter = "powershell-*$macosRuntime.tar.gz" - $tarPkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $tarPkgNameFilter -Recurse -File | Select-Object -ExpandProperty FullName - Write-Host "##vso[artifact.upload containerfolder=macos-pkgs;artifactname=macos-pkgs]$tarPkgPath" + Write-Verbose -Verbose "Looking for tar packages with filter: $tarPkgNameFilter in '$(Pipeline.Workspace)' to upload..." + $tarPkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $tarPkgNameFilter -Recurse -File + + foreach($t in $tarPkgPath) { + $file = $t.FullName + $entry = & tar -tzvf $file | Where-Object { $_ -match '\spwsh$' } | Select-Object -First 1 + if ($entry -notmatch '^-..x') { + throw "pwsh is not executable in $file : $entry" + } + Write-Verbose -verbose "Uploading $file to macos-pkgs" + Write-Host "##vso[artifact.upload containerfolder=macos-pkgs;artifactname=macos-pkgs]$file" + } + + $packageInfo = Get-MacOSPackageIdentifierInfo -Version '$(Version)' -LTS:$LTS + Write-Verbose -Verbose "BundleId: $($packageInfo.PackageIdentifier)" + Write-Host "##vso[task.setvariable variable=BundleId;isOutput=true]$($packageInfo.PackageIdentifier)" displayName: 'Package ${{ parameters.buildArchitecture}}' + name: packageStep env: __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) @@ -136,6 +188,7 @@ jobs: type: windows variables: + - group: certificate_logical_to_actual - name: ob_outputDirectory value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' - name: ob_sdl_binskim_enabled @@ -144,7 +197,8 @@ jobs: value: $(Build.SourcesDirectory)/PowerShell/.config/suppress.json - name: BuildArch value: ${{ parameters.buildArchitecture }} - - group: mscodehub-macos-package-signing + - name: BundleId + value: $[ dependencies.package_macOS_${{ parameters.buildArchitecture }}.outputs['packageStep.BundleId'] ] steps: - download: current @@ -182,32 +236,59 @@ jobs: inline_operation: | [ { - "KeyCode": "$(KeyCode)", + "KeyCode": "$(apple_cert_id)", "OperationCode": "MacAppDeveloperSign", "ToolName": "sign", "ToolVersion": "1.0", "Parameters": { - "Hardening": "Enable", - "OpusInfo": "http://microsoft.com" + "Hardening": "--options=runtime" } } ] + - task: onebranch.pipeline.signing@1 + displayName: 'OneBranch Notarize Package' + inputs: + command: 'sign' + files_to_sign: '**/*-osx-*.zip' + search_root: '$(Pipeline.Workspace)' + inline_operation: | + [ + { + "KeyCode": "$(apple_cert_id)", + "OperationCode": "MacAppNotarize", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": { + "BundleId": "$(BundleId)" + } + } + ] + timeoutInMinutes: 120 + - pwsh: | $signedPkg = Get-ChildItem -Path $(Pipeline.Workspace) -Filter "*osx*.zip" -File - + + if (-not (Test-Path $(ob_outputDirectory))) { + $null = New-Item -Path $(ob_outputDirectory) -ItemType Directory + } + + $expandDir = "$(Pipeline.Workspace)/pkgExpand" + $null = New-Item -Path $expandDir -ItemType Directory -Force + $signedPkg | ForEach-Object { Write-Verbose -Verbose "Signed package zip: $_" - - if (-not (Test-Path $_)) { - throw "Package not found: $_" - } - - if (-not (Test-Path $(ob_outputDirectory))) { - $null = New-Item -Path $(ob_outputDirectory) -ItemType Directory - } + Expand-Archive -Path $_ -DestinationPath $expandDir -Verbose + } + + # ESRP's signing pipeline nests the PKG inside a '.zip.unzipped' subfolder + $pkgFile = Get-ChildItem -Path $expandDir -Filter '*.pkg' -Recurse -File + if (-not $pkgFile) { + throw "Package not found in: $signedPkg" + } - Expand-Archive -Path $_ -DestinationPath $(ob_outputDirectory) -Verbose + $pkgFile | ForEach-Object { + Move-Item -Path $_ -Destination $(ob_outputDirectory) -Verbose } Write-Verbose -Verbose "Expanded pkg file:" diff --git a/.pipelines/templates/mac.yml b/.pipelines/templates/mac.yml index 310c5695979..cd492994617 100644 --- a/.pipelines/templates/mac.yml +++ b/.pipelines/templates/mac.yml @@ -13,8 +13,6 @@ jobs: variables: - name: HOMEBREW_NO_ANALYTICS value: 1 - - name: runCodesignValidationInjection - value: false - name: NugetSecurityAnalysisWarningLevel value: none - group: DotNetPrivateBuildAccess @@ -39,12 +37,8 @@ jobs: sudo chown $env:USER "$(Agent.TempDirectory)/PowerShell" displayName: 'Create $(Agent.TempDirectory)/PowerShell' - - task: UseDotNet@2 - displayName: 'Use .NET Core sdk' - inputs: - useGlobalJson: true - packageType: 'sdk' - workingDirectory: $(PowerShellRoot) + ## We cross compile for arm64, so the arch is always x64 + - template: /.pipelines/templates/install-dotnet.yml@self - pwsh: | Import-Module $(PowerShellRoot)/build.psm1 -Force @@ -75,6 +69,14 @@ jobs: $psOptPath = "$(OB_OUTPUTDIRECTORY)/psoptions.json" Save-PSOptions -PSOptionsPath $psOptPath + $entitlements = "$(PowerShellRoot)/assets/macos-entitlements.plist" + $pwshBin = "$(OB_OUTPUTDIRECTORY)/pwsh" + Write-Verbose -Verbose "Applying entitlements to $pwshBin" + codesign --sign - --force --options runtime --entitlements $entitlements $pwshBin + if ($LASTEXITCODE -ne 0) { + throw "codesign failed with exit code $LASTEXITCODE" + } + # Since we are using custom pool for macOS, we need to use artifact.upload to publish the artifacts Write-Host "##vso[artifact.upload containerfolder=$artifactName;artifactname=$artifactName]$(OB_OUTPUTDIRECTORY)" @@ -148,5 +150,38 @@ jobs: - template: /.pipelines/templates/obp-file-signing.yml@self parameters: binPath: $(DropRootPath) + OfficialBuild: $(ps_official_build) + + # Apple-sign the Mach-O binaries inside the signed output. + - pwsh: | + $signedDir = "$(ob_outputDirectory)/Signed-$(Runtime)" + $zipFile = "$(Pipeline.Workspace)/macho-$(BuildArchitecture).zip" + Compress-Archive -Path "$signedDir/*" -DestinationPath $zipFile -Force + displayName: Compress signed folder for Apple signing + + - task: onebranch.pipeline.signing@1 + displayName: Apple CodeSign Mach-O binaries + inputs: + command: 'sign' + files_to_sign: 'macho-$(BuildArchitecture).zip' + search_root: '$(Pipeline.Workspace)' + inline_operation: | + [ + { + "KeyCode": "$(apple_cert_id)", + "OperationCode": "MacAppDeveloperSign", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": { + "Hardening": "--options=runtime" + } + } + ] + + - pwsh: | + $signedDir = "$(ob_outputDirectory)/Signed-$(Runtime)" + $zipFile = "$(Pipeline.Workspace)/macho-$(BuildArchitecture).zip" + Expand-Archive -Path $zipFile -DestinationPath $signedDir -Force -Verbose + displayName: Expand Apple-signed Mach-O binaries into signed output - template: /.pipelines/templates/step/finalize.yml@self diff --git a/.pipelines/templates/nupkg.yml b/.pipelines/templates/nupkg.yml index dc43e841332..043c0b1f75c 100644 --- a/.pipelines/templates/nupkg.yml +++ b/.pipelines/templates/nupkg.yml @@ -6,8 +6,6 @@ jobs: type: windows variables: - - name: runCodesignValidationInjection - value: false - name: nugetMultiFeedWarnLevel value: none - name: NugetSecurityAnalysisWarningLevel @@ -25,6 +23,7 @@ jobs: - group: mscodehub-feed-read-general - group: mscodehub-feed-read-akv - group: DotNetPrivateBuildAccess + - group: certificate_logical_to_actual steps: - checkout: self @@ -41,8 +40,7 @@ jobs: - template: SetVersionVariables.yml@self parameters: ReleaseTagVar: $(ReleaseTagVar) - CreateJson: yes - UseJson: no + CreateJson: no - template: shouldSign.yml @@ -94,15 +92,7 @@ jobs: parameters: repoRoot: $(PowerShellRoot) - - task: NuGetToolInstaller@1 - displayName: 'Install NuGet.exe' - - - task: UseDotNet@2 - displayName: 'Use .NET Core sdk' - inputs: - useGlobalJson: true - packageType: 'sdk' - workingDirectory: '$(PowerShellRoot)' + - template: /.pipelines/templates/install-dotnet.yml@self - pwsh: | Set-Location -Path '$(PowerShellRoot)' @@ -128,13 +118,13 @@ jobs: Start-PSBuild -Clean -Runtime linux-x64 -Configuration Release -ReleaseTag $(ReleaseTagVar) $sharedModules | Foreach-Object { - $refFile = Get-ChildItem -Path "$(PowerShellRoot)\src\$_\obj\Release\net10.0\refint\$_.dll" + $refFile = Get-ChildItem -Path "$(PowerShellRoot)\src\$_\obj\Release\net11.0\refint\$_.dll" Write-Verbose -Verbose "RefAssembly: $refFile" Copy-Item -Path $refFile -Destination "$refAssemblyFolder\$_.dll" -Verbose - $refDoc = "$(PowerShellRoot)\src\$_\bin\Release\net10.0\$_.xml" + $refDoc = "$(PowerShellRoot)\src\$_\bin\Release\net11.0\$_.xml" if (-not (Test-Path $refDoc)) { Write-Warning "$refDoc not found" - Get-ChildItem -Path "$(PowerShellRoot)\src\$_\bin\Release\net10.0\" | Out-String | Write-Verbose -Verbose + Get-ChildItem -Path "$(PowerShellRoot)\src\$_\bin\Release\net11.0\" | Out-String | Write-Verbose -Verbose } else { Copy-Item -Path $refDoc -Destination "$refAssemblyFolder\$_.xml" -Verbose @@ -144,13 +134,13 @@ jobs: Start-PSBuild -Clean -Runtime win7-x64 -Configuration Release -ReleaseTag $(ReleaseTagVar) $winOnlyModules | Foreach-Object { - $refFile = Get-ChildItem -Path "$(PowerShellRoot)\src\$_\obj\Release\net10.0\refint\*.dll" + $refFile = Get-ChildItem -Path "$(PowerShellRoot)\src\$_\obj\Release\net11.0\refint\*.dll" Write-Verbose -Verbose 'RefAssembly: $refFile' Copy-Item -Path $refFile -Destination "$refAssemblyFolder\$_.dll" -Verbose - $refDoc = "$(PowerShellRoot)\src\$_\bin\Release\net10.0\$_.xml" + $refDoc = "$(PowerShellRoot)\src\$_\bin\Release\net11.0\$_.xml" if (-not (Test-Path $refDoc)) { Write-Warning "$refDoc not found" - Get-ChildItem -Path "$(PowerShellRoot)\src\$_\bin\Release\net10.0" | Out-String | Write-Verbose -Verbose + Get-ChildItem -Path "$(PowerShellRoot)\src\$_\bin\Release\net11.0" | Out-String | Write-Verbose -Verbose } else { Copy-Item -Path $refDoc -Destination "$refAssemblyFolder\$_.xml" -Verbose @@ -219,7 +209,7 @@ jobs: displayName: Sign nupkg files inputs: command: 'sign' - cp_code: 'CP-401405' + cp_code: '$(nuget_cert_id)' files_to_sign: '**\*.nupkg' search_root: '$(Pipeline.Workspace)\nupkg' @@ -279,7 +269,7 @@ jobs: displayName: Sign nupkg files inputs: command: 'sign' - cp_code: 'CP-401405' + cp_code: '$(nuget_cert_id)' files_to_sign: '**\*.nupkg' search_root: '$(Pipeline.Workspace)\globaltools' diff --git a/.pipelines/templates/obp-file-signing.yml b/.pipelines/templates/obp-file-signing.yml index b6683d3caaf..cbe44ad0018 100644 --- a/.pipelines/templates/obp-file-signing.yml +++ b/.pipelines/templates/obp-file-signing.yml @@ -1,6 +1,9 @@ parameters: binPath: '$(ob_outputDirectory)' globalTool: 'false' + SigningProfile: 'external_distribution' + OfficialBuild: true + vPackScenario: false steps: - pwsh: | @@ -80,7 +83,7 @@ steps: displayName: Sign 1st party files inputs: command: 'sign' - signing_profile: external_distribution + signing_profile: ${{ parameters.SigningProfile }} files_to_sign: '**\*.psd1;**\*.psm1;**\*.ps1xml;**\*.ps1;**\*.dll;**\*.exe;**\pwsh' search_root: $(Pipeline.Workspace)/toBeSigned @@ -95,12 +98,15 @@ steps: $BuildPath = (Get-Item '${{ parameters.binPath }}').FullName Write-Verbose -Verbose -Message "BuildPath: $BuildPath" + $officialBuild = [System.Convert]::ToBoolean('${{ parameters.OfficialBuild }}') ## copy all files to be signed to build folder - Update-PSSignedBuildFolder -BuildPath $BuildPath -SignedFilesPath '$(Pipeline.Workspace)/toBeSigned' + Update-PSSignedBuildFolder -BuildPath $BuildPath -SignedFilesPath '$(Pipeline.Workspace)/toBeSigned' -OfficialBuild $officialBuild $dlls = Get-ChildItem $BuildPath/*.dll, $BuildPath/*.exe -Recurse $signatures = $dlls | Get-AuthenticodeSignature - $missingSignatures = $signatures | Where-Object { $_.status -eq 'notsigned' -or $_.SignerCertificate.Issuer -notmatch '^CN=Microsoft.*'}| select-object -ExpandProperty Path + $officialIssuerPattern = '^CN=(Microsoft Code Signing PCA|Microsoft Root Certificate Authority|Microsoft Corporation).*' + $testCert = '^CN=(Microsoft|TestAzureEngBuildCodeSign).*' + $missingSignatures = $signatures | Where-Object { $_.status -eq 'notsigned' -or $_.SignerCertificate.Issuer -notmatch $testCert -or $_.SignerCertificate.Issuer -notmatch $officialIssuerPattern} | select-object -ExpandProperty Path Write-Verbose -verbose "to be signed:`r`n $($missingSignatures | Out-String)" @@ -137,11 +143,20 @@ steps: displayName: Capture ThirdParty Signed files - pwsh: | + $officialBuild = [System.Convert]::ToBoolean('${{ parameters.OfficialBuild }}') + $vPackScenario = [System.Convert]::ToBoolean('${{ parameters.vPackScenario }}') Import-Module '$(PowerShellRoot)/build.psm1' -Force Import-Module '$(PowerShellRoot)/tools/packaging' -Force $isGlobalTool = '${{ parameters.globalTool }}' -eq 'true' - if (-not $isGlobalTool) { + if ($vPackScenario) { + Write-Verbose -Verbose -Message "vPackScenario is true, copying to $(ob_outputDirectory)" + $pathForUpload = New-Item -ItemType Directory -Path '$(ob_outputDirectory)' -Force + Write-Verbose -Verbose -Message "pathForUpload: $pathForUpload" + Copy-Item -Path '${{ parameters.binPath }}\*' -Destination $pathForUpload -Recurse -Force -Verbose + Write-Verbose -Verbose -Message "Files copied to $pathForUpload" + } + elseif (-not $isGlobalTool) { $pathForUpload = New-Item -ItemType Directory -Path '$(ob_outputDirectory)/Signed-$(Runtime)' -Force Write-Verbose -Verbose -Message "pathForUpload: $pathForUpload" Copy-Item -Path '${{ parameters.binPath }}\*' -Destination $pathForUpload -Recurse -Force -Verbose @@ -153,7 +168,7 @@ steps: Write-Verbose "Copying third party signed files to the build folder" $thirdPartySignedFilesPath = (Get-Item '$(Pipeline.Workspace)/thirdPartyToBeSigned').FullName - Update-PSSignedBuildFolder -BuildPath $pathForUpload -SignedFilesPath $thirdPartySignedFilesPath + Update-PSSignedBuildFolder -BuildPath $pathForUpload -SignedFilesPath $thirdPartySignedFilesPath -OfficialBuild $officialBuild displayName: 'Copy signed files for upload' diff --git a/.pipelines/templates/package-create-msix.yml b/.pipelines/templates/package-create-msix.yml index 0ab2408e6a7..97d2f4fc46a 100644 --- a/.pipelines/templates/package-create-msix.yml +++ b/.pipelines/templates/package-create-msix.yml @@ -1,3 +1,8 @@ +parameters: + - name: OfficialBuild + type: boolean + default: false + jobs: - job: CreateMSIXBundle displayName: Create .msixbundle file @@ -7,16 +12,25 @@ jobs: variables: - group: msixTools - group: 'Azure Blob variable group' + - name: ob_sdl_credscan_suppressionsFile + value: $(Build.SourcesDirectory)\PowerShell\.config\suppress.json + - name: ob_sdl_tsa_configFile + value: $(Build.SourcesDirectory)\PowerShell\.config\tsaoptions.json - name: ob_outputDirectory value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' steps: + - checkout: self + clean: true + env: + ob_restore_phase: true # This ensures checkout is done at the beginning of the restore phase + - template: release-SetReleaseTagandContainerName.yml@self - task: DownloadPipelineArtifact@2 inputs: buildType: 'current' - artifact: drop_windows_package_package_win_arm64 + artifact: drop_windows_package_arm64 itemPattern: | **/*.msix targetPath: '$(Build.ArtifactStagingDirectory)/downloads' @@ -25,7 +39,7 @@ jobs: - task: DownloadPipelineArtifact@2 inputs: buildType: 'current' - artifact: drop_windows_package_package_win_x64 + artifact: drop_windows_package_x64 itemPattern: | **/*.msix targetPath: '$(Build.ArtifactStagingDirectory)/downloads' @@ -34,12 +48,12 @@ jobs: - task: DownloadPipelineArtifact@2 inputs: buildType: 'current' - artifact: drop_windows_package_package_win_x86 + artifact: drop_windows_package_x86 itemPattern: | **/*.msix targetPath: '$(Build.ArtifactStagingDirectory)/downloads' displayName: Download windows x86 packages - + # Finds the makeappx tool on the machine with image: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' - pwsh: | $cmd = Get-Command makeappx.exe -ErrorAction Ignore @@ -70,17 +84,44 @@ jobs: $null = Copy-Item -Path $msixFile.FullName -Destination $sourceDir -Force -Verbose } - $file = Get-ChildItem $sourceDir | Select-Object -First 1 - $prefix = ($file.BaseName -split "-win")[0] - $pkgName = "$prefix.msixbundle" - Write-Verbose -Verbose "Creating $pkgName" - $makeappx = '$(MakeAppxPath)' $outputDir = "$sourceDir\output" New-Item $outputDir -Type Directory -Force > $null - & $makeappx bundle /d $sourceDir /p "$outputDir\$pkgName" - Get-ChildItem -Path $sourceDir -Recurse + # Separate LTS and Stable/Preview MSIX files by filename convention + $ltsMsix = @(Get-ChildItem $sourceDir -Filter '*.msix' | Where-Object { $_.BaseName -match '-LTS-' }) + $stableMsix = @(Get-ChildItem $sourceDir -Filter '*.msix' | Where-Object { $_.BaseName -notmatch '-LTS-' }) + + Write-Verbose -Verbose "Stable/Preview MSIX files: $($stableMsix.Name -join ', ')" + Write-Verbose -Verbose "LTS MSIX files: $($ltsMsix.Name -join ', ')" + + # Create Stable/Preview bundle + if ($stableMsix.Count -gt 0) { + $stableDir = "$sourceDir\stable" + New-Item $stableDir -Type Directory -Force > $null + $stableMsix | Copy-Item -Destination $stableDir -Force + $file = $stableMsix | Select-Object -First 1 + $prefix = ($file.BaseName -split "-win")[0] + $stableBundleName = "$prefix.msixbundle" + Write-Verbose -Verbose "Creating Stable/Preview bundle: $stableBundleName" + & $makeappx bundle /d $stableDir /p "$outputDir\$stableBundleName" + } + + # Create LTS bundle + if ($ltsMsix.Count -gt 0) { + $ltsDir = "$sourceDir\lts" + New-Item $ltsDir -Type Directory -Force > $null + $ltsMsix | Copy-Item -Destination $ltsDir -Force + $file = $ltsMsix | Select-Object -First 1 + $prefix = ($file.BaseName -split "-win")[0] + $ltsBundleName = "$prefix.msixbundle" + Write-Verbose -Verbose "Creating LTS bundle: $ltsBundleName" + & $makeappx bundle /d $ltsDir /p "$outputDir\$ltsBundleName" + } + + Write-Verbose -Verbose "Created bundles:" + Get-ChildItem -Path $outputDir -Recurse + $vstsCommandString = "vso[task.setvariable variable=BundleDir]$outputDir" Write-Host "sending " + $vstsCommandString Write-Host "##$vstsCommandString" @@ -89,22 +130,25 @@ jobs: - task: onebranch.pipeline.signing@1 displayName: Sign MsixBundle + condition: eq('${{ parameters.OfficialBuild }}', 'true') inputs: command: 'sign' signing_profile: $(MSIXProfile) files_to_sign: '**/*.msixbundle' search_root: '$(BundleDir)' - + - pwsh: | - $signedBundle = Get-ChildItem -Path $(BundleDir) -Filter "*.msixbundle" -File - Write-Verbose -Verbose "Signed bundle: $signedBundle" + $signedBundles = @(Get-ChildItem -Path $(BundleDir) -Filter "*.msixbundle" -File) + Write-Verbose -Verbose "Signed bundles: $($signedBundles.Name -join ', ')" if (-not (Test-Path $(ob_outputDirectory))) { New-Item -ItemType Directory -Path $(ob_outputDirectory) -Force } - Copy-Item -Path $signedBundle.FullName -Destination "$(ob_outputDirectory)" -Verbose + foreach ($bundle in $signedBundles) { + Copy-Item -Path $bundle.FullName -Destination "$(ob_outputDirectory)" -Verbose + } - Write-Verbose -Verbose "Uploaded Bundle:" + Write-Verbose -Verbose "Uploaded Bundles:" Get-ChildItem -Path $(ob_outputDirectory) | Write-Verbose -Verbose displayName: Upload msixbundle to Artifacts diff --git a/.pipelines/templates/package-store-package.yml b/.pipelines/templates/package-store-package.yml new file mode 100644 index 00000000000..6abddae6851 --- /dev/null +++ b/.pipelines/templates/package-store-package.yml @@ -0,0 +1,244 @@ +jobs: +- job: CreateStorePackage + displayName: Create StoreBroker Package + pool: + type: windows + + variables: + - group: 'Azure Blob variable group' + - group: 'Store Publish Variables' + - name: ob_sdl_credscan_suppressionsFile + value: $(Build.SourcesDirectory)\PowerShell\.config\suppress.json + - name: ob_sdl_tsa_configFile + value: $(Build.SourcesDirectory)\PowerShell\.config\tsaoptions.json + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' + - name: ob_signing_setup_enabled + value: false + - name: ob_sdl_codeSignValidation_enabled + value: false + + steps: + - checkout: self + clean: true + + - template: release-SetReleaseTagandContainerName.yml@self + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifact: drop_msixbundle_CreateMSIXBundle + itemPattern: | + **/*.msixbundle + targetPath: '$(Build.ArtifactStagingDirectory)/downloads' + displayName: Download signed msixbundle + + - pwsh: | + $bundleDir = '$(Build.ArtifactStagingDirectory)/downloads' + $bundle = Get-ChildItem -Path $bundleDir -Filter '*.msixbundle' -Recurse | Select-Object -First 1 + if (-not $bundle) { + Write-Error "No .msixbundle file found in $bundleDir" + exit 1 + } + Write-Verbose -Verbose "Found bundle: $($bundle.FullName)" + $vstsCommandString = "vso[task.setvariable variable=BundleDir]$($bundle.DirectoryName)" + Write-Host "##$vstsCommandString" + displayName: Locate msixbundle + + - template: channelSelection.yml@self + + - pwsh: | + $IsLTS = '$(ChannelSelection.IsLTS)' -eq 'true' + $IsStable = '$(ChannelSelection.IsStable)' -eq 'true' + $IsPreview = '$(ChannelSelection.IsPreview)' -eq 'true' + + Write-Verbose -Verbose "Channel Selection - LTS: $IsLTS, Stable: $IsStable, Preview: $IsPreview" + + # Define app configurations for each channel + $channelConfigs = @{ + 'LTS' = @{ + AppStoreName = 'PowerShell-LTS' + ProductId = '$(productId-LTS)' + AppId = '$(AppID-LTS)' + ServiceEndpoint = "StoreAppPublish-Stable" + } + 'Stable' = @{ + AppStoreName = 'PowerShell' + ProductId = '$(productId-Stable)' + AppId = '$(AppID-Stable)' + ServiceEndpoint = "StoreAppPublish-Stable" + } + 'Preview' = @{ + AppStoreName = 'PowerShell (Preview)' + ProductId = '$(productId-Preview)' + AppId = '$(AppID-Preview)' + ServiceEndpoint = "StoreAppPublish-Preview" + } + } + + $currentChannel = if ($IsLTS) { 'LTS' } + elseif ($IsStable) { 'Stable' } + elseif ($IsPreview) { 'Preview' } + else { + Write-Error "No valid channel detected" + exit 1 + } + + $config = $channelConfigs[$currentChannel] + Write-Verbose -Verbose "Selected channel: $currentChannel" + Write-Verbose -Verbose "App Store Name: $($config.AppStoreName)" + Write-Verbose -Verbose "Product ID: $($config.ProductId)" + + # Update PDP.xml file + $pdpPath = '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/PDP/PDP/en-US/PDP.xml' + if (Test-Path $pdpPath) { + Write-Verbose -Verbose "Updating PDP file: $pdpPath" + + [xml]$pdpXml = Get-Content $pdpPath -Raw + + # Create namespace manager for XML with default namespace + $nsManager = New-Object System.Xml.XmlNamespaceManager($pdpXml.NameTable) + $nsManager.AddNamespace("pd", "http://schemas.microsoft.com/appx/2012/ProductDescription") + + $appStoreNameElement = $pdpXml.SelectSingleNode("//pd:AppStoreName", $nsManager) + if ($appStoreNameElement) { + $appStoreNameElement.SetAttribute("_locID", $config.AppStoreName) + Write-Verbose -Verbose "Updated AppStoreName _locID to: $($config.AppStoreName)" + } else { + Write-Warning "AppStoreName element not found in PDP file" + } + + $pdpXml.Save($pdpPath) + Write-Verbose -Verbose "PDP file updated successfully" + Get-Content -Path $pdpPath | Write-Verbose -Verbose + } else { + Write-Error "PDP file not found: $pdpPath" + exit 1 + } + + # Update SBConfig.json file + $sbConfigPath = '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/SBConfig.json' + if (Test-Path $sbConfigPath) { + Write-Verbose -Verbose "Updating SBConfig file: $sbConfigPath" + + $sbConfigJson = Get-Content $sbConfigPath -Raw | ConvertFrom-Json + + $sbConfigJson.appSubmission.productId = $config.ProductId + Write-Verbose -Verbose "Updated productId to: $($config.ProductId)" + + $sbConfigJson | ConvertTo-Json -Depth 100 | Set-Content $sbConfigPath -Encoding UTF8 + Write-Verbose -Verbose "SBConfig file updated successfully" + Get-Content -Path $sbConfigPath | Write-Verbose -Verbose + } else { + Write-Error "SBConfig file not found: $sbConfigPath" + exit 1 + } + + Write-Host "##vso[task.setvariable variable=ServiceConnection]$($config.ServiceEndpoint)" + Write-Host "##vso[task.setvariable variable=SBConfigPath]$($sbConfigPath)" + + # Select the correct bundle based on channel + $bundleFiles = @(Get-ChildItem -Path '$(BundleDir)' -Filter '*.msixbundle') + Write-Verbose -Verbose "Available bundles: $($bundleFiles.Name -join ', ')" + + if ($IsLTS) { + $bundleFile = $bundleFiles | Where-Object { $_.Name -match '-LTS-' } + } else { + # Catches Stable or Preview + $bundleFile = $bundleFiles | Where-Object { $_.Name -notmatch '-LTS-' } + } + + if (-not $bundleFile) { + Write-Error "No matching bundle found for channel '$currentChannel'. Available bundles: $($bundleFiles.Name -join ', ')" + exit 1 + } + + # Copy the selected bundle to a dedicated directory for store packaging + $storeBundleDir = '$(Pipeline.Workspace)\releasePipeline\msix\store-bundle' + New-Item $storeBundleDir -Type Directory -Force > $null + Copy-Item -Path $bundleFile.FullName -Destination $storeBundleDir -Force -Verbose + Write-Host "##vso[task.setvariable variable=StoreBundleDir]$storeBundleDir" + Write-Verbose -Verbose "Selected bundle for store packaging: $($bundleFile.Name)" + + # These variables are used in the next tasks to determine which ServiceEndpoint to use + $ltsValue = $IsLTS.ToString().ToLower() + $stableValue = $IsStable.ToString().ToLower() + $previewValue = $IsPreview.ToString().ToLower() + + Write-Verbose -Verbose "About to set variables:" + Write-Verbose -Verbose " LTS=$ltsValue" + Write-Verbose -Verbose " STABLE=$stableValue" + Write-Verbose -Verbose " PREVIEW=$previewValue" + + Write-Host "##vso[task.setvariable variable=LTS]$ltsValue" + Write-Host "##vso[task.setvariable variable=STABLE]$stableValue" + Write-Host "##vso[task.setvariable variable=PREVIEW]$previewValue" + + Write-Verbose -Verbose "Variables set successfully" + name: UpdateConfigs + displayName: Update PDPs and SBConfig.json + + - pwsh: | + Write-Verbose -Verbose "Checking variables after UpdateConfigs:" + Write-Verbose -Verbose "LTS=$(LTS)" + Write-Verbose -Verbose "STABLE=$(STABLE)" + Write-Verbose -Verbose "PREVIEW=$(PREVIEW)" + displayName: Debug - Check Variables + + - task: MS-RDX-MRO.windows-store-publish.package-task.store-package@3 + displayName: 'Create StoreBroker Package (Preview)' + condition: eq(variables['PREVIEW'], 'true') + inputs: + serviceEndpoint: 'StoreAppPublish-Preview' + sbConfigPath: '$(SBConfigPath)' + sourceFolder: '$(StoreBundleDir)' + contents: '*.msixBundle' + outSBName: 'PowerShellStorePackage' + pdpPath: '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/PDP/PDP' + pdpMediaPath: '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/PDP/PDP-Media' + + - task: MS-RDX-MRO.windows-store-publish.package-task.store-package@3 + displayName: 'Create StoreBroker Package (Stable/LTS)' + condition: or(eq(variables['STABLE'], 'true'), eq(variables['LTS'], 'true')) + inputs: + serviceEndpoint: 'StoreAppPublish-Stable' + sbConfigPath: '$(SBConfigPath)' + sourceFolder: '$(StoreBundleDir)' + contents: '*.msixBundle' + outSBName: 'PowerShellStorePackage' + pdpPath: '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/PDP/PDP' + pdpMediaPath: '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/PDP/PDP-Media' + + - pwsh: | + $outputDirectory = "$(ob_outputDirectory)" + if (-not (Test-Path -LiteralPath $outputDirectory)) { + New-Item -ItemType Directory -Path $outputDirectory -Force | Out-Null + } + + Get-Item -Path "$(System.DefaultWorkingDirectory)/SBLog.txt" -ErrorAction SilentlyContinue | + Copy-Item -Destination $outputDirectory -Verbose + displayName: Upload Store Failure Log + condition: failed() + + - pwsh: | + $outputDirectory = "$(ob_outputDirectory)" + if (-not (Test-Path -LiteralPath $outputDirectory)) { + New-Item -ItemType Directory -Path $outputDirectory -Force | Out-Null + } + + $submissionPackageDir = "$(System.DefaultWorkingDirectory)/SBOutDir" + $jsonFile = "$submissionPackageDir/PowerShellStorePackage.json" + $zipFile = "$submissionPackageDir/PowerShellStorePackage.zip" + + if ((Test-Path $jsonFile) -and (Test-Path $zipFile)) { + Write-Verbose -Verbose "Uploading StoreBroker Package files:" + Write-Verbose -Verbose "JSON File: $jsonFile" + Write-Verbose -Verbose "ZIP File: $zipFile" + + Copy-Item -Path $submissionPackageDir -Destination $outputDirectory -Verbose -Recurse + } + else { + Write-Error "Required files not found in $submissionPackageDir" + exit 1 + } + displayName: 'Upload StoreBroker Package' diff --git a/.pipelines/templates/windows-package-build.yml b/.pipelines/templates/packaging/windows/package.yml similarity index 50% rename from .pipelines/templates/windows-package-build.yml rename to .pipelines/templates/packaging/windows/package.yml index 53b57df45dd..8b0e230b6b8 100644 --- a/.pipelines/templates/windows-package-build.yml +++ b/.pipelines/templates/packaging/windows/package.yml @@ -2,15 +2,19 @@ parameters: runtime: x64 jobs: -- job: package_win_${{ parameters.runtime }} - displayName: Package Windows ${{ parameters.runtime }} +- job: build_win_${{ parameters.runtime }} + displayName: Build Windows Packages ${{ parameters.runtime }} condition: succeeded() pool: type: windows variables: - - name: runCodesignValidationInjection - value: false + - name: ob_sdl_codeSignValidation_enabled + value: false # Skip signing validation in build-only stage + - name: ob_signing_setup_enabled + value: false # Disable signing setup - this is a build-only stage, signing happens in separate stage + - name: ob_artifactBaseName + value: drop_windows_package_${{ parameters.runtime }} - name: nugetMultiFeedWarnLevel value: none - name: NugetSecurityAnalysisWarningLevel @@ -22,7 +26,7 @@ jobs: - name: ob_outputDirectory value: '$(Build.ArtifactStagingDirectory)\ONEBRANCH_ARTIFACT' - name: ob_sdl_binskim_enabled - value: true + value: false # Disable for build-only, enable in signing stage - name: ob_sdl_tsa_configFile value: $(Build.SourcesDirectory)\PowerShell\.config\tsaoptions.json - name: ob_sdl_credscan_suppressionsFile @@ -34,40 +38,37 @@ jobs: steps: - checkout: self clean: true - env: - ob_restore_phase: true # This ensures checkout is done at the beginning of the restore phase - pwsh: | Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose displayName: Capture environment - env: - ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue - - template: SetVersionVariables.yml@self + - template: /.pipelines/templates/SetVersionVariables.yml@self parameters: ReleaseTagVar: $(ReleaseTagVar) - CreateJson: yes - UseJson: no + CreateJson: no + ob_restore_phase: false - - template: shouldSign.yml + - template: /.pipelines/templates/shouldSign.yml@self + parameters: + ob_restore_phase: false - - template: cloneToOfficialPath.yml + - template: /.pipelines/templates/cloneToOfficialPath.yml@self parameters: nativePathRoot: '$(Agent.TempDirectory)' + ob_restore_phase: false + + - template: /.pipelines/templates/rebuild-branch-check.yml@self - download: CoOrdinatedBuildPipeline artifact: drop_windows_build_windows_${{ parameters.runtime }}_release displayName: Download signed artifacts condition: ${{ ne(parameters.runtime, 'minSize') }} - env: - ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue - download: CoOrdinatedBuildPipeline artifact: drop_windows_build_windows_x64_${{ parameters.runtime }} displayName: Download minsize signed artifacts condition: ${{ eq(parameters.runtime, 'minSize') }} - env: - ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue - pwsh: | Write-Verbose -Verbose "signed artifacts" @@ -75,23 +76,10 @@ jobs: displayName: 'Capture Downloaded Artifacts' # Diagnostics is not critical it passes every time it runs continueOnError: true - env: - ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue - - task: UseDotNet@2 - inputs: - useGlobalJson: true - workingDirectory: $(REPOROOT) - env: - ob_restore_phase: true - - - pwsh: | - $msixUrl = '$(makeappUrl)' - Invoke-RestMethod -Uri $msixUrl -OutFile '$(Pipeline.Workspace)\makeappx.zip' - Expand-Archive '$(Pipeline.Workspace)\makeappx.zip' -destination '\' -Force - displayName: Install packaging tools - env: - ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue + - template: /.pipelines/templates/install-dotnet.yml@self + parameters: + ob_restore_phase: false - pwsh: | $runtime = '$(Runtime)' @@ -112,7 +100,9 @@ jobs: Import-Module "$repoRoot\build.psm1" Import-Module "$repoRoot\tools\packaging" - Start-PSBootstrap -Scenario Package + Start-PSBootstrap -Scenario Both + + Find-Dotnet $signedFilesPath, $psoptionsFilePath = if ($env:RUNTIME -eq 'minsize') { "$(Pipeline.Workspace)\CoOrdinatedBuildPipeline\drop_windows_build_windows_x64_${runtime}\$signedFolder" @@ -137,7 +127,23 @@ jobs: Get-PSOptions | Write-Verbose -Verbose $metadata = Get-Content "$repoRoot/tools/metadata.json" -Raw | ConvertFrom-Json - $LTS = $metadata.LTSRelease.Package + + Write-Verbose -Verbose "metadata:" + $metadata | Out-String | Write-Verbose -Verbose + + # Use the rebuild branch check from the template + $isRebuildBranch = '$(RebuildBranchCheck.IsRebuildBranch)' -eq 'true' + + # Don't build LTS packages for rebuild branches + $LTS = $metadata.LTSRelease.Package -and -not $isRebuildBranch + $Stable = [bool]$metadata.StableRelease.Package + + if ($isRebuildBranch) { + Write-Verbose -Message "Rebuild branch detected, skipping LTS package build" -Verbose + } + + Write-Verbose -Verbose "LTS: $LTS" + Write-Verbose -Verbose "Stable: $Stable" if ($LTS) { Write-Verbose -Message "LTS Release: $LTS" @@ -155,9 +161,9 @@ jobs: } $packageTypes = switch ($runtime) { - 'x64' { @('msi', 'zip', 'msix') } - 'x86' { @('msi', 'zip', 'msix') } - 'arm64' { @('msi', 'zip', 'msix') } + 'x64' { @('zip', 'msix') } + 'x86' { @('zip', 'msix') } + 'arm64' { @('zip', 'msix') } 'fxdependent' { 'fxdependent' } 'fxdependentWinDesktop' { 'fxdependent-win-desktop' } 'minsize' { 'min-size' } @@ -171,94 +177,25 @@ jobs: Start-PSPackage -Type $packageTypes -SkipReleaseChecks -WindowsRuntime $WindowsRuntime -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath -LTS:$LTS - displayName: 'Package ${{ parameters.buildArchitecture}}' - env: - __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) - ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue - - - task: onebranch.pipeline.signing@1 - displayName: Sign MSI packages - inputs: - command: 'sign' - signing_profile: external_distribution - files_to_sign: '**\*.msi' - search_root: '$(Pipeline.Workspace)' - - - pwsh: | - $runtime = '$(Runtime)' - Write-Verbose -Verbose "runtime = '$(Runtime)'" - - $repoRoot = "$env:REPOROOT" - Import-Module "$repoRoot\build.psm1" - Import-Module "$repoRoot\tools\packaging" - - $noExeRuntimes = @('fxdependent', 'fxdependentWinDesktop', 'minsize') - - if ($runtime -in $noExeRuntimes) { - Write-Verbose -Verbose "No EXE generated for $runtime" - return - } - - $version = '$(Version)' - - $msiLocation = Get-ChildItem -Path $(Pipeline.Workspace) -Recurse -Filter "powershell-*$runtime.msi" | Select-Object -ExpandProperty FullName - Write-Verbose -Verbose "msiLocation: $msiLocation" - - Set-Location $repoRoot - - $exePath = New-ExePackage -ProductVersion $version -ProductTargetArchitecture $runtime -MsiLocationPath $msiLocation - Write-Verbose -Verbose "setting vso[task.setvariable variable=exePath]$exePath" - Write-Host "##vso[task.setvariable variable=exePath]$exePath" - Write-Verbose -Verbose "exePath: $exePath" - - $enginePath = Join-Path -Path '$(System.ArtifactsDirectory)\unsignedEngine' -ChildPath engine.exe - Expand-ExePackageEngine -ExePath $exePath -EnginePath $enginePath -ProductTargetArchitecture $runtime - displayName: 'Make exe and expand package' - - - task: onebranch.pipeline.signing@1 - displayName: Sign exe engine - inputs: - command: 'sign' - signing_profile: $(msft_3rd_party_cert_id) - files_to_sign: '$(System.ArtifactsDirectory)\unsignedEngine\*.exe' - search_root: '$(Pipeline.Workspace)' - - - pwsh: | - $runtime = '$(Runtime)' - Write-Verbose -Verbose "runtime = '$(Runtime)'" - $repoRoot = "$env:REPOROOT" - Import-Module "$repoRoot\build.psm1" - Import-Module "$repoRoot\tools\packaging" - - $noExeRuntimes = @('fxdependent', 'fxdependentWinDesktop', 'minsize') - - if ($runtime -in $noExeRuntimes) { - Write-Verbose -Verbose "No EXE generated for $runtime" - return + # When both LTS and Stable are requested, also build the Stable MSIX + if ($packageTypes -contains 'msix' -and $LTS -and $Stable) { + Write-Verbose -Verbose "Both LTS and Stable packages requested. Building additional Stable MSIX." + Start-PSPackage -Type msix -SkipReleaseChecks -WindowsRuntime $WindowsRuntime -ReleaseTag $(ReleaseTagVar) -PackageBinPath $signedFilesPath } - $exePath = '$(exePath)' - $enginePath = Join-Path -Path '$(System.ArtifactsDirectory)\unsignedEngine' -ChildPath engine.exe - $enginePath | Get-AuthenticodeSignature | out-string | Write-Verbose -verbose - Compress-ExePackageEngine -ExePath $exePath -EnginePath $enginePath -ProductTargetArchitecture $runtime - displayName: Compress signed exe package - - - task: onebranch.pipeline.signing@1 - displayName: Sign exe packages - inputs: - command: 'sign' - signing_profile: external_distribution - files_to_sign: '**\*.exe' - search_root: '$(Pipeline.Workspace)' + displayName: 'Build Packages (Unsigned)' + env: + __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) + # Copy unsigned packages to output directory - pwsh: | $runtime = '$(Runtime)' Write-Verbose -Verbose "runtime = '$(Runtime)'" $packageTypes = switch ($runtime) { - 'x64' { @('msi', 'zip', 'msix', 'exe') } - 'x86' { @('msi', 'zip', 'msix', 'exe') } - 'arm64' { @('msi', 'zip', 'msix', 'exe') } + 'x64' { @('zip', 'msix') } + 'x86' { @('zip', 'msix') } + 'arm64' { @('zip', 'msix') } 'fxdependent' { 'fxdependent' } 'fxdependentWinDesktop' { 'fxdependent-win-desktop' } 'minsize' { 'min-size' } @@ -268,38 +205,21 @@ jobs: New-Item -ItemType Directory -Path $(ob_outputDirectory) -Force } - if ($packageTypes -contains 'msi') { - $msiPkgNameFilter = "powershell-*.msi" - $msiPkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $msiPkgNameFilter -Recurse -File | Select-Object -ExpandProperty FullName - Write-Verbose -Verbose "msiPkgPath: $msiPkgPath" - Copy-Item -Path $msiPkgPath -Destination '$(ob_outputDirectory)' -Force -Verbose - } - - if ($packageTypes -contains 'exe') { - $msiPkgNameFilter = "powershell-*.exe" - $msiPkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $msiPkgNameFilter -Recurse -File | Select-Object -ExpandProperty FullName - Write-Verbose -Verbose "msiPkgPath: $msiPkgPath" - Copy-Item -Path $msiPkgPath -Destination '$(ob_outputDirectory)' -Force -Verbose - } - if ($packageTypes -contains 'zip' -or $packageTypes -contains 'fxdependent' -or $packageTypes -contains 'min-size' -or $packageTypes -contains 'fxdependent-win-desktop') { $zipPkgNameFilter = "powershell-*.zip" $zipPkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $zipPkgNameFilter -Recurse -File | Select-Object -ExpandProperty FullName - Write-Verbose -Verbose "zipPkgPath: $zipPkgPath" + Write-Verbose -Verbose "unsigned zipPkgPath: $zipPkgPath" Copy-Item -Path $zipPkgPath -Destination '$(ob_outputDirectory)' -Force -Verbose } if ($packageTypes -contains 'msix') { - $msixPkgNameFilter = "powershell-*.msix" + $msixPkgNameFilter = "PowerShell*.msix" $msixPkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $msixPkgNameFilter -Recurse -File | Select-Object -ExpandProperty FullName - Write-Verbose -Verbose "msixPkgPath: $msixPkgPath" + Write-Verbose -Verbose "unsigned msixPkgPath: $msixPkgPath" Copy-Item -Path $msixPkgPath -Destination '$(ob_outputDirectory)' -Force -Verbose } - displayName: Copy to output directory + displayName: Copy unsigned packages to output directory - pwsh: | Get-ChildItem -Path $(ob_outputDirectory) -Recurse - displayName: 'List artifacts' - env: - ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue - + displayName: 'List unsigned artifacts' diff --git a/.pipelines/templates/packaging/windows/sign.yml b/.pipelines/templates/packaging/windows/sign.yml new file mode 100644 index 00000000000..151b5d676fb --- /dev/null +++ b/.pipelines/templates/packaging/windows/sign.yml @@ -0,0 +1,117 @@ +parameters: + runtime: x64 + +jobs: +- job: sign_win_${{ parameters.runtime }} + displayName: Sign Windows Packages ${{ parameters.runtime }} + condition: succeeded() + pool: + type: windows + + variables: + - name: runCodesignValidationInjection + value: false + - name: ob_artifactBaseName + value: drop_windows_package_package_win_${{ parameters.runtime }} + - name: nugetMultiFeedWarnLevel + value: none + - name: NugetSecurityAnalysisWarningLevel + value: none + - name: skipNugetSecurityAnalysis + value: true + - group: DotNetPrivateBuildAccess + - group: certificate_logical_to_actual + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)\ONEBRANCH_ARTIFACT' + - name: ob_sdl_binskim_enabled + value: true + - name: ob_sdl_tsa_configFile + value: $(Build.SourcesDirectory)\PowerShell\.config\tsaoptions.json + - name: ob_sdl_credscan_suppressionsFile + value: $(Build.SourcesDirectory)\PowerShell\.config\suppress.json + - name: Runtime + value: ${{ parameters.runtime }} + - group: msixTools + + steps: + - checkout: self + clean: true + env: + ob_restore_phase: true + + - template: /.pipelines/templates/SetVersionVariables.yml@self + parameters: + ReleaseTagVar: $(ReleaseTagVar) + CreateJson: no + + - template: /.pipelines/templates/shouldSign.yml@self + + - template: /.pipelines/templates/cloneToOfficialPath.yml@self + parameters: + nativePathRoot: '$(Agent.TempDirectory)' + + # Download unsigned packages from the build stage + - download: current + artifact: drop_windows_package_${{ parameters.runtime }} + displayName: Download unsigned packages + env: + ob_restore_phase: true + + - pwsh: | + Write-Verbose -Verbose "Downloaded unsigned artifacts:" + Get-ChildItem "$(Pipeline.Workspace)\drop_windows_package_${{ parameters.runtime }}" -Recurse + displayName: 'Capture Downloaded Unsigned Artifacts' + continueOnError: true + env: + ob_restore_phase: true + + - template: /.pipelines/templates/install-dotnet.yml@self + + # Import build.psm1 and bootstrap packaging dependencies + - pwsh: | + $repoRoot = "$env:REPOROOT" + Import-Module "$repoRoot\build.psm1" + Import-Module "$repoRoot\tools\packaging" + Write-Verbose -Verbose "Modules imported successfully" + displayName: 'Import modules' + env: + ob_restore_phase: true + + # Copy all signed packages to output directory + - pwsh: | + $runtime = '$(Runtime)' + Write-Verbose -Verbose "runtime = '$(Runtime)'" + + $packageTypes = switch ($runtime) { + 'x64' { @('zip', 'msix') } + 'x86' { @('zip', 'msix') } + 'arm64' { @('zip', 'msix') } + 'fxdependent' { 'fxdependent' } + 'fxdependentWinDesktop' { 'fxdependent-win-desktop' } + 'minsize' { 'min-size' } + } + + if (-not (Test-Path $(ob_outputDirectory))) { + New-Item -ItemType Directory -Path $(ob_outputDirectory) -Force + } + + if ($packageTypes -contains 'zip' -or $packageTypes -contains 'fxdependent' -or $packageTypes -contains 'min-size' -or $packageTypes -contains 'fxdependent-win-desktop') { + $zipPkgNameFilter = "powershell-*.zip" + $zipPkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $zipPkgNameFilter -Recurse -File | Select-Object -ExpandProperty FullName + Write-Verbose -Verbose "signed zipPkgPath: $zipPkgPath" + Copy-Item -Path $zipPkgPath -Destination '$(ob_outputDirectory)' -Force -Verbose + } + + if ($packageTypes -contains 'msix') { + $msixPkgNameFilter = "PowerShell*.msix" + $msixPkgPath = Get-ChildItem -Path $(Pipeline.Workspace) -Filter $msixPkgNameFilter -Recurse -File | Select-Object -ExpandProperty FullName + Write-Verbose -Verbose "signed msixPkgPath: $msixPkgPath" + Copy-Item -Path $msixPkgPath -Destination '$(ob_outputDirectory)' -Force -Verbose + } + displayName: Copy signed packages to output directory + + - pwsh: | + Get-ChildItem -Path $(ob_outputDirectory) -Recurse + displayName: 'List signed artifacts' + env: + ob_restore_phase: true diff --git a/.pipelines/templates/rebuild-branch-check.yml b/.pipelines/templates/rebuild-branch-check.yml new file mode 100644 index 00000000000..a4b546a0dc6 --- /dev/null +++ b/.pipelines/templates/rebuild-branch-check.yml @@ -0,0 +1,17 @@ +# This template checks if the current branch is a rebuild branch +# and sets an output variable IsRebuildBranch that can be used by other templates +steps: +- pwsh: | + # Check if this is a rebuild branch (e.g., rebuild/v7.4.13-rebuild.5) + $isRebuildBranch = '$(Build.SourceBranch)' -match 'refs/heads/rebuild/.*-rebuild\.' + + $value = if ($isRebuildBranch) { 'true' } else { 'false' } + Write-Verbose -Message "IsRebuildBranch: $value" -Verbose + + if ($isRebuildBranch) { + Write-Verbose -Message "Rebuild branch detected: $(Build.SourceBranch)" -Verbose + } + + Write-Host "##vso[task.setvariable variable=IsRebuildBranch;isOutput=true]$value" + name: RebuildBranchCheck + displayName: Check if Rebuild Branch diff --git a/.pipelines/templates/release-MSIX-Publish.yml b/.pipelines/templates/release-MSIX-Publish.yml new file mode 100644 index 00000000000..cbbdb70cc4f --- /dev/null +++ b/.pipelines/templates/release-MSIX-Publish.yml @@ -0,0 +1,138 @@ +parameters: + - name: skipMSIXPublish + type: boolean + +jobs: +- job: Store_Publish_MSIX + displayName: Publish MSIX to the Microsoft Store + pool: + type: release + os: windows + templateContext: + inputs: + - input: pipelineArtifact + pipeline: PSPackagesOfficial + artifactName: drop_store_package_CreateStorePackage + variables: + - group: 'Store Publish Variables' + - name: LTS + value: $[ stageDependencies.setReleaseTagAndChangelog.setTagAndChangelog.outputs['ChannelSelection.IsLTS'] ] + - name: STABLE + value: $[ stageDependencies.setReleaseTagAndChangelog.setTagAndChangelog.outputs['ChannelSelection.IsStable'] ] + - name: PREVIEW + value: $[ stageDependencies.setReleaseTagAndChangelog.setTagAndChangelog.outputs['ChannelSelection.IsPreview'] ] + - template: ./variables/release-shared.yml@self + parameters: + RELEASETAG: $[ stageDependencies.setReleaseTagAndChangelog.setTagAndChangelog.outputs['OutputReleaseTag.releaseTag'] ] + steps: + - task: PowerShell@2 + inputs: + targetType: inline + script: | + Write-Verbose -Verbose "Release Tag: $(ReleaseTag)" + Get-ChildItem $(Pipeline.Workspace) -Recurse | Select-Object -ExpandProperty FullName + displayName: 'Capture ReleaseTag and Downloaded Packages' + + - task: PowerShell@2 + inputs: + targetType: inline + script: | + if ("$(ReleaseTag)" -eq '') { + Write-Error "ReleaseTag is not set. Cannot proceed with publishing to the Store." + exit 1 + } + $middleURL = '' + $tagString = "$(ReleaseTag)" + if ($tagString -match '-preview') { + $middleURL = "preview" + } + elseif ($tagString -match '(\d+\.\d+)') { + $middleURL = $matches[1] + } + + $endURL = $tagString -replace '^v','' -replace '\.','' + $message = "Changelog: https://github.com/PowerShell/PowerShell/blob/master/CHANGELOG/$middleURL.md#$endURL" + Write-Verbose -Verbose "Release Notes for the Store:" + Write-Verbose -Verbose "$message" + $jsonPath = "$(Pipeline.Workspace)\SBOutDir\PowerShellStorePackage.json" + $json = Get-Content $jsonPath -Raw | ConvertFrom-Json + + $json.listings.'en-us'.baseListing.releaseNotes = $message + + # Add PowerShell version to the top of the description + $description = $json.listings.'en-us'.baseListing.description + $version = "$(ReleaseTag)" + $updatedDescription = "Version: $version`n`n$description" + $json.listings.'en-us'.baseListing.description = $updatedDescription + Write-Verbose -Verbose "Updated description: $updatedDescription" + + $json | ConvertTo-Json -Depth 100 | Set-Content $jsonPath -Encoding UTF8 + displayName: 'Add Changelog Link and Version Number to SBJSON' + + - task: PowerShell@2 + inputs: + targetType: inline + script: | + # Convert ADO variables to PowerShell boolean variables + $IsLTS = '$(LTS)' -eq 'true' + $IsStable = '$(STABLE)' -eq 'true' + $IsPreview = '$(PREVIEW)' -eq 'true' + + Write-Verbose -Verbose "Channel Selection - LTS: $(LTS), Stable: $(STABLE), Preview: $(PREVIEW)" + + $currentChannel = if ($IsLTS) { 'LTS' } + elseif ($IsStable) { 'Stable' } + elseif ($IsPreview) { 'Preview' } + else { + Write-Error "No valid channel detected" + exit 1 + } + + # Assign AppID for Store-Publish Task + $appID = $null + if ($IsLTS) { + $appID = '$(AppID-LTS)' + } + elseif ($IsStable) { + $appID = '$(AppID-Stable)' + } + else { + $appID = '$(AppID-Preview)' + } + + Write-Host "##vso[task.setvariable variable=AppID]$appID" + Write-Verbose -Verbose "Selected channel: $currentChannel" + Write-Verbose -Verbose "Conditional tasks will handle the publishing based on channel variables" + displayName: 'Validate Channel Selection' + + - task: MS-RDX-MRO.windows-store-publish.publish-task.store-publish@3 + displayName: 'Publish StoreBroker Package (Stable/LTS)' + condition: and(not(${{ parameters.skipMSIXPublish }}), or(eq(variables['STABLE'], 'true'), eq(variables['LTS'], 'true'))) + inputs: + serviceEndpoint: 'StoreAppPublish-Stable' + appId: '$(AppID)' + inputMethod: JsonAndZip + jsonPath: '$(Pipeline.Workspace)\SBOutDir\PowerShellStorePackage.json' + zipPath: '$(Pipeline.Workspace)\SBOutDir\PowerShellStorePackage.zip' + force: true + deletePackages: true + numberOfPackagesToKeep: 2 + jsonZipUpdateMetadata: true + targetPublishMode: 'Immediate' + skipPolling: true + + - task: MS-RDX-MRO.windows-store-publish.publish-task.store-publish@3 + displayName: 'Publish StoreBroker Package (Preview)' + condition: and(not(${{ parameters.skipMSIXPublish }}), eq(variables['PREVIEW'], 'true')) + inputs: + serviceEndpoint: 'StoreAppPublish-Preview' + appId: '$(AppID)' + inputMethod: JsonAndZip + jsonPath: '$(Pipeline.Workspace)\SBOutDir\PowerShellStorePackage.json' + zipPath: '$(Pipeline.Workspace)\SBOutDir\PowerShellStorePackage.zip' + force: true + deletePackages: true + numberOfPackagesToKeep: 2 + jsonZipUpdateMetadata: true + targetPublishMode: 'Immediate' + skipPolling: true diff --git a/.pipelines/templates/release-MakeBlobPublic.yml b/.pipelines/templates/release-MakeBlobPublic.yml index c8f12938d25..758298202a1 100644 --- a/.pipelines/templates/release-MakeBlobPublic.yml +++ b/.pipelines/templates/release-MakeBlobPublic.yml @@ -45,8 +45,7 @@ jobs: - template: /.pipelines/templates/SetVersionVariables.yml@self parameters: ReleaseTagVar: $(ReleaseTagVar) - CreateJson: yes - UseJson: no + CreateJson: no - pwsh: | Get-ChildItem Env: @@ -132,8 +131,7 @@ jobs: - template: /.pipelines/templates/SetVersionVariables.yml@self parameters: ReleaseTagVar: $(ReleaseTagVar) - CreateJson: yes - UseJson: no + CreateJson: no - pwsh: | Get-ChildItem Env: | Out-String -width 9999 -Stream | write-Verbose -Verbose diff --git a/.pipelines/templates/release-SetTagAndChangelog.yml b/.pipelines/templates/release-SetTagAndChangelog.yml index f0c516dd28f..b33e652b3c7 100644 --- a/.pipelines/templates/release-SetTagAndChangelog.yml +++ b/.pipelines/templates/release-SetTagAndChangelog.yml @@ -19,7 +19,7 @@ jobs: clean: true env: ob_restore_phase: true - + - pwsh: | Write-Verbose -Verbose "Release Tag: $(OutputReleaseTag.releaseTag)" $releaseVersion = '$(OutputReleaseTag.releaseTag)' -replace '^v','' @@ -47,3 +47,5 @@ jobs: New-Item -Path $(ob_outputDirectory)/CHANGELOG -ItemType Directory -Force Copy-Item -Path $filePath -Destination $(ob_outputDirectory)/CHANGELOG displayName: Upload Changelog + + - template: channelSelection.yml@self diff --git a/.pipelines/templates/release-checkout-pwsh-repo.yml b/.pipelines/templates/release-checkout-pwsh-repo.yml deleted file mode 100644 index 9a7486887a6..00000000000 --- a/.pipelines/templates/release-checkout-pwsh-repo.yml +++ /dev/null @@ -1,13 +0,0 @@ -steps: - - pwsh: | - Write-Verbose -Verbose "Deploy Box Product Pathway Does Not Support the `"checkout`" task" - if ($ENV:BUILD_REASON -eq 'PullRequest') { - throw 'We dont support PRs' - } - - Write-Verbose -Verbose $ENV:BUILD_SOURCEBRANCH - $branchName = $ENV:BUILD_SOURCEBRANCH -replace '^refs/heads/' - Write-Verbose -Verbose "Branch Name: $branchName" - git clone --depth 1 --branch $branchName https://$(mscodehubCodeReadPat)@mscodehub.visualstudio.com/PowerShellCore/_git/PowerShell '$(Pipeline.Workspace)/PowerShell' - cd $(Pipeline.Workspace)/PowerShell - displayName: Checkout Powershell Repository diff --git a/.pipelines/templates/release-download-packages.yml b/.pipelines/templates/release-download-packages.yml deleted file mode 100644 index 27a3098d1e1..00000000000 --- a/.pipelines/templates/release-download-packages.yml +++ /dev/null @@ -1,122 +0,0 @@ -jobs: -- job: upload_packages - displayName: Upload packages - condition: succeeded() - pool: - type: windows - variables: - - template: ./variable/release-shared.yml@self - parameters: - REPOROOT: $(Build.SourcesDirectory) - SBOM: true - - steps: - - pwsh: | - Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose - displayName: Capture environment variables - - - download: PSPackagesOfficial - artifact: drop_linux_package_deb - displayName: Download linux deb packages - - - download: PSPackagesOfficial - artifact: drop_linux_package_fxdependent - displayName: Download linux fx packages - - - download: PSPackagesOfficial - artifact: drop_linux_package_mariner_arm64 - displayName: Download linux mariner packages - - - download: PSPackagesOfficial - artifact: drop_linux_package_mariner_x64 - displayName: Download linux mariner x64 packages - - - download: PSPackagesOfficial - artifact: drop_linux_package_minSize - displayName: Download linux min packages - - - download: PSPackagesOfficial - artifact: drop_linux_package_rpm - displayName: Download linux rpm packages - - - download: PSPackagesOfficial - artifact: drop_linux_package_tar - displayName: Download linux tar packages - - - download: PSPackagesOfficial - artifact: drop_linux_package_tar_alpine - displayName: Download linux tar alpine packages - - - download: PSPackagesOfficial - artifact: drop_linux_package_tar_alpine_fxd - displayName: Download linux tar alpine fxd packages - - - download: PSPackagesOfficial - artifact: drop_linux_package_tar_arm - displayName: Download linux tar arm packages - - - download: PSPackagesOfficial - artifact: drop_linux_package_tar_arm64 - displayName: Download linux tar arm 64 packages - - - download: PSPackagesOfficial - artifact: drop_nupkg_build_nupkg - displayName: Download nupkg packages - - - download: PSPackagesOfficial - artifact: drop_windows_package_package_win_arm64 - displayName: Download windows arm64 packages - - - download: PSPackagesOfficial - artifact: drop_windows_package_package_win_fxdependent - displayName: Download windows fxdependent packages - - - download: PSPackagesOfficial - artifact: drop_windows_package_package_win_fxdependentWinDesktop - displayName: Download windows fxdependentWinDesktop packages - - - download: PSPackagesOfficial - artifact: drop_windows_package_package_win_minsize - displayName: Download windows minsize packages - - - download: PSPackagesOfficial - artifact: drop_windows_package_package_win_x64 - displayName: Download windows x64 packages - - - download: PSPackagesOfficial - artifact: drop_windows_package_package_win_x86 - displayName: Download windows x86 packages - - - download: PSPackagesOfficial - artifact: macos-pkgs - displayName: Download macos tar packages - - - download: PSPackagesOfficial - artifact: drop_mac_package_sign_package_macos_arm64 - displayName: Download macos arm packages - - - download: PSPackagesOfficial - artifact: drop_mac_package_sign_package_macos_x64 - displayName: Download macos x64 packages - - - pwsh: | - Get-ChildItem '$(Pipeline.Workspace)/PSPackagesOfficial' -Recurse | Select-Object -ExpandProperty FullName - displayName: 'Capture downloads' - - - pwsh: | - $PackagesPath = '$(Pipeline.Workspace)/PSPackagesOfficial' - Write-Verbose -Verbose "Copying Github Release files in $PackagesPath to use in Release Pipeline" - - Write-Verbose -Verbose "Creating output directory for GitHub Release files: $(ob_outputDirectory)/GitHubPackages" - New-Item -Path $(ob_outputDirectory)/GitHubPackages -ItemType Directory -Force - Get-ChildItem -Path "$PackagesPath/*" -Recurse | - Where-Object { $_.Extension -notin '.msix', '.nupkg' } | - Where-Object { $_.Extension -in '.gz', '.pkg', '.msi', '.zip', '.deb', '.rpm', '.zip' } | - Copy-Item -Destination $(ob_outputDirectory)/GitHubPackages -Recurse -Verbose - - Write-Verbose -Verbose "Creating output directory for NuGet packages: $(ob_outputDirectory)/NuGetPackages" - New-Item -Path $(ob_outputDirectory)/NuGetPackages -ItemType Directory -Force - Get-ChildItem -Path "$PackagesPath/*" -Recurse | - Where-Object { $_.Extension -eq '.nupkg' } | - Copy-Item -Destination $(ob_outputDirectory)/NuGetPackages -Recurse -Verbose - displayName: Copy downloads to Artifacts diff --git a/.pipelines/templates/release-githubNuget.yml b/.pipelines/templates/release-githubNuget.yml index a8a874619a4..27a358c7e75 100644 --- a/.pipelines/templates/release-githubNuget.yml +++ b/.pipelines/templates/release-githubNuget.yml @@ -17,7 +17,7 @@ jobs: pipeline: PSPackagesOfficial artifactName: drop_upload_upload_packages variables: - - template: ./variable/release-shared.yml@self + - template: ./variables/release-shared.yml@self parameters: RELEASETAG: $[ stageDependencies.setReleaseTagAndChangelog.setTagAndChangelog.outputs['OutputReleaseTag.releaseTag'] ] @@ -35,6 +35,21 @@ jobs: targetType: inline script: | $Path = "$(Pipeline.Workspace)/GitHubPackages" + + # The .exe packages are for Windows Update only and should not be uploaded to GitHub release. + $exefiles = Get-ChildItem -Path $Path -Filter *.exe + if ($exefiles) { + Write-Verbose -Verbose "Remove .exe packages:" + $exefiles | Remove-Item -Force -Verbose + } + + # The .msi packages should not be uploaded to GitHub release. + $msifiles = Get-ChildItem -Path $Path -Filter *.msi + if ($msifiles) { + Write-Verbose -Verbose "Remove .msi packages:" + $msifiles | Remove-Item -Force -Verbose + } + $OutputPath = Join-Path $Path 'hashes.sha256' $packages = Get-ChildItem -Path $Path -Include * -Recurse -File $checksums = $packages | @@ -74,11 +89,21 @@ jobs: $changelog = Get-Content -Path $filePath $headingPattern = "^## \[\d+\.\d+\.\d+" - $headingStartLines = $changelog | Select-String -Pattern $headingPattern | Select-Object -ExpandProperty LineNumber + $headingStartLines = @($changelog | Select-String -Pattern $headingPattern | Select-Object -ExpandProperty LineNumber) + + if ($headingStartLines.Count -eq 0) { + throw "No release heading matching '$headingPattern' found in $filePath" + } + $startLine = $headingStartLines[0] - $endLine = $headingStartLines[1] - 1 - - $clContent = $changelog | Select-Object -Skip ($startLine-1) -First ($endLine - $startLine) | Out-String + if ($headingStartLines.Count -ge 2) { + $endLine = $headingStartLines[1] - 1 + } else { + # Only one release heading present; take through end of file. + $endLine = $changelog.Count + } + + $clContent = $changelog | Select-Object -Skip ($startLine-1) -First ($endLine - $startLine + 1) | Out-String $StringBuilder = [System.Text.StringBuilder]::new($clContent, $clContent.Length + 2kb) $StringBuilder.AppendLine().AppendLine() > $null @@ -121,13 +146,13 @@ jobs: $middleURL = '' $tagString = "$(ReleaseTag)" Write-Verbose -Verbose "Use the following command to push the tag:" - if ($tagString -match '-') { + if ($tagString -match '-preview') { $middleURL = "preview" } elseif ($tagString -match '(\d+\.\d+)') { $middleURL = $matches[1] } - $endURL = $tagString -replace '[v\.]','' + $endURL = $tagString -replace '^v|\.', '' $message = "https://github.com/PowerShell/PowerShell/blob/master/CHANGELOG/$middleURL.md#$endURL" Write-Verbose -Verbose "git tag -a $(ReleaseTag) $env:BUILD_SOURCEVERSION -m $message" displayName: Git Push Tag Command @@ -159,7 +184,7 @@ jobs: pipeline: PSPackagesOfficial artifactName: drop_upload_upload_packages variables: - - template: ./variable/release-shared.yml@self + - template: ./variables/release-shared.yml@self parameters: VERSION: $[ stageDependencies.setReleaseTagAndChangelog.SetTagAndChangelog.outputs['OutputVersion.Version'] ] diff --git a/.pipelines/templates/release-install-pwsh.yml b/.pipelines/templates/release-install-pwsh.yml deleted file mode 100644 index 9d7080a7e78..00000000000 --- a/.pipelines/templates/release-install-pwsh.yml +++ /dev/null @@ -1,34 +0,0 @@ -steps: - - task: PowerShell@2 - inputs: - targetType: inline - script: | - $localInstallerPath = Get-ChildItem -Path "$(Pipeline.Workspace)/GitHubPackages" -Filter '*win-x64.msi' | Select-Object -First 1 -ExpandProperty FullName - if (Test-Path -Path $localInstallerPath) { - Write-Verbose -Verbose "Installer found at $localInstallerPath" - } else { - throw "Installer not found" - } - Write-Verbose -Verbose "Installing PowerShell via msiexec" - Start-Process -FilePath msiexec -ArgumentList "/package $localInstallerPath /quiet REGISTER_MANIFEST=1" -Wait -NoNewWindow - $pwshPath = Get-ChildItem -Directory -Path 'C:\Program Files\PowerShell\7*' | Select-Object -First 1 -ExpandProperty FullName - if (Test-Path -Path $pwshPath) { - Write-Verbose -Verbose "PowerShell installed at $pwshPath" - Write-Verbose -Verbose "Adding pwsh to env:PATH" - Write-Host "##vso[task.prependpath]$pwshPath" - } else { - throw "PowerShell not installed" - } - displayName: Install pwsh 7 - - - task: PowerShell@2 - inputs: - targetType: inline - pwsh: true - script: | - Write-Verbose -Verbose "Pwsh 7 Installed" - Write-Verbose -Verbose "env:Path: " - $env:PATH -split ';' | ForEach-Object { - Write-Verbose -Verbose $_ - } - displayName: Check pwsh 7 installation diff --git a/.pipelines/templates/release-prep-for-ev2.yml b/.pipelines/templates/release-prep-for-ev2.yml index cf7982cd5e1..3ad716a3af4 100644 --- a/.pipelines/templates/release-prep-for-ev2.yml +++ b/.pipelines/templates/release-prep-for-ev2.yml @@ -11,6 +11,20 @@ stages: displayName: 'Copy EV2 Files to Artifact' pool: type: linux + templateContext: + inputs: + - input: pipelineArtifact + pipeline: PSPackagesOfficial + artifactName: drop_linux_package_deb + - input: pipelineArtifact + pipeline: PSPackagesOfficial + artifactName: drop_linux_package_rpm + - input: pipelineArtifact + pipeline: PSPackagesOfficial + artifactName: drop_linux_package_mariner_x64 + - input: pipelineArtifact + pipeline: PSPackagesOfficial + artifactName: drop_linux_package_mariner_arm64 variables: - name: ob_outputDirectory value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' @@ -23,7 +37,9 @@ stages: - group: 'mscodehub-code-read-akv' - group: 'packages.microsoft.com' - name: ob_sdl_credscan_suppressionsFile - value: $(Build.SourcesDirectory)\PowerShell\.config\suppress.json + value: $(Build.SourcesDirectory)/PowerShell/.config/suppress.json + - name: ob_sdl_tsa_configFile + value: $(Build.SourcesDirectory)/PowerShell/.config/tsaoptions.json steps: - checkout: self ## the global setting on lfs didn't work lfs: false @@ -33,7 +49,7 @@ stages: - template: release-SetReleaseTagandContainerName.yml parameters: restorePhase: true - + - pwsh: | $packageVersion = '$(OutputReleaseTag.ReleaseTag)'.ToLowerInvariant() -replace '^v','' $vstsCommandString = "vso[task.setvariable variable=packageVersion]$packageVersion" @@ -42,7 +58,7 @@ stages: displayName: Set Package version env: ob_restore_phase: true - + - pwsh: | $branch = 'mirror-target' $gitArgs = "clone", @@ -99,39 +115,17 @@ stages: env: ob_restore_phase: true - - download: PSPackagesOfficial - artifact: 'drop_linux_package_deb' - displayName: 'Download artifact containing .deb_amd64.deb file from PSPackagesOfficial triggering pipeline' - env: - ob_restore_phase: true - - - download: PSPackagesOfficial - artifact: 'drop_linux_package_rpm' - displayName: 'Download artifact containing .rh.x64_86.rpm file from PSPackagesOfficial triggering pipeline' - env: - ob_restore_phase: true - - - download: PSPackagesOfficial - artifact: 'drop_linux_package_mariner_x64' - displayName: 'Download artifact containing .cm.x86_64.rpm file from PSPackagesOfficial triggering pipeline' - env: - ob_restore_phase: true - - - download: PSPackagesOfficial - artifact: 'drop_linux_package_mariner_arm64' - displayName: 'Download artifact containing .cm.aarch64.rpm file from PSPackagesOfficial triggering pipeline' - env: - ob_restore_phase: true - - pwsh: | Write-Verbose -Verbose "Copy ESRP signed .deb and .rpm packages" - $downloadedPipelineFolder = Join-Path '$(Pipeline.Workspace)' -ChildPath 'PSPackagesOfficial' + # templateContext.inputs places the PSPackagesOfficial pipelineArtifact files + # directly under $(Pipeline.Workspace), not in per-artifact subfolders. + $downloadedPipelineFolder = '$(Pipeline.Workspace)' $srcFilesFolder = Join-Path -Path '$(Pipeline.Workspace)' -ChildPath 'SourceFiles' New-Item -Path $srcFilesFolder -ItemType Directory $packagesFolder = Join-Path -Path $srcFilesFolder -ChildPath 'packages' New-Item -Path $packagesFolder -ItemType Directory - $packageFiles = Get-ChildItem -Path $downloadedPipelineFolder -Recurse -Directory -Filter "drop_*" | Get-ChildItem -File -Include *.deb, *.rpm + $packageFiles = Get-ChildItem -Path $downloadedPipelineFolder -File | Where-Object { $_.Extension -in '.deb', '.rpm' } foreach ($file in $packageFiles) { Write-Verbose -Verbose "copying file: $($file.FullName)" @@ -151,7 +145,7 @@ stages: $metadataHash = @{} $skipPublishValue = '${{ parameters.skipPublish }}' $metadataHash["ReleaseTag"] = '$(OutputReleaseTag.ReleaseTag)' - $metadataHash["LTS"] = $metadata.LTSRelease.Latest + $metadataHash["LTS"] = $metadata.LTSRelease.PublishToChannels $metadataHash["ForProduction"] = $true $metadataHash["SkipPublish"] = [System.Convert]::ToBoolean($skipPublishValue) @@ -222,7 +216,7 @@ stages: files_to_sign: '*.ps1' search_root: '$(repoRoot)/.pipelines/EV2Specs/ServiceGroupRoot/Shell/Run' displayName: Sign Run.ps1 - + - pwsh: | # folder to tar must have: Run.ps1, settings.toml, python_dl $srcPath = Join-Path '$(ev2ServiceGroupRootFolder)' -ChildPath 'Shell' diff --git a/.pipelines/templates/release-publish-pmc.yml b/.pipelines/templates/release-publish-pmc.yml index d5454845211..dc7fc8534e3 100644 --- a/.pipelines/templates/release-publish-pmc.yml +++ b/.pipelines/templates/release-publish-pmc.yml @@ -1,37 +1,56 @@ +parameters: +- name: releaseEnvironment + type: string + default: Production + values: + - Production + - PPE + - Test +- name: approvalServiceEnvironment + type: string + default: Production + values: + - Production + - PPE + - Test +# OneBranch requires the stage name to be prefixed with the release environment. +# Official uses 'Prod' for Production; NonProd validators require '' (e.g. 'Test', 'PPE'). +- name: stagePrefix + type: string + default: Prod +# When true, the Ev2 push step is skipped. Useful for NonOfficial dry-runs that +# only want to validate artifact download via templateContext.inputs. +- name: skipEv2Push + type: boolean + default: false + stages: -- stage: 'Prod_Release' +- stage: ${{ parameters.stagePrefix }}_Release displayName: 'Deploy packages to PMC with EV2' dependsOn: - PrepForEV2 variables: - name: ob_release_environment - value: "Production" + value: ${{ parameters.releaseEnvironment }} - name: repoRoot value: $(Build.SourcesDirectory) jobs: - - job: Prod_ReleaseJob + - job: ${{ parameters.stagePrefix }}_ReleaseJob displayName: Publish to PMC pool: type: release - - steps: - - task: DownloadPipelineArtifact@2 + templateContext: inputs: - targetPath: '$(Pipeline.Workspace)' - artifact: drop_PrepForEV2_CopyEv2FilesToArtifact - displayName: 'Download drop_PrepForEV2_CopyEv2FilesToArtifact artifact that has all files needed' + - input: pipelineArtifact + artifactName: drop_PrepForEV2_CopyEv2FilesToArtifact - - task: DownloadPipelineArtifact@2 - inputs: - buildType: 'current' - targetPath: '$(Pipeline.Workspace)' - displayName: 'Download to get EV2 Files' - - - task: vsrm-ev2.vss-services-ev2.adm-release-task.ExpressV2Internal@1 - displayName: 'Ev2: Push to PMC' - inputs: - UseServerMonitorTask: true - EndpointProviderType: ApprovalService - ApprovalServiceEnvironment: Production - ServiceRootPath: '$(Pipeline.Workspace)/drop_PrepForEV2_CopyEV2FilesToArtifact/EV2Specs/ServiceGroupRoot' - RolloutSpecPath: '$(Pipeline.Workspace)/drop_PrepForEV2_CopyEV2FilesToArtifact/EV2Specs/ServiceGroupRoot/RolloutSpec.json' + steps: + - ${{ if not(parameters.skipEv2Push) }}: + - task: vsrm-ev2.vss-services-ev2.adm-release-task.ExpressV2Internal@1 + displayName: 'Ev2: Push to PMC' + inputs: + UseServerMonitorTask: true + EndpointProviderType: ApprovalService + ApprovalServiceEnvironment: ${{ parameters.approvalServiceEnvironment }} + ServiceRootPath: '$(Pipeline.Workspace)/EV2Specs/ServiceGroupRoot' + RolloutSpecPath: '$(Pipeline.Workspace)/EV2Specs/ServiceGroupRoot/RolloutSpec.json' diff --git a/.pipelines/templates/release-symbols.yml b/.pipelines/templates/release-symbols.yml index 9bfa7b870d4..a628f4d7127 100644 --- a/.pipelines/templates/release-symbols.yml +++ b/.pipelines/templates/release-symbols.yml @@ -10,11 +10,9 @@ jobs: pool: type: windows variables: - - name: runCodesignValidationInjection - value: false - name: NugetSecurityAnalysisWarningLevel value: none - - name: DOTNET_SKIP_FIRST_TIME_EXPERIENCE + - name: DOTNET_NOLOGO value: 1 - name: ob_outputDirectory value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' diff --git a/.pipelines/templates/release-upload-buildinfo.yml b/.pipelines/templates/release-upload-buildinfo.yml index c8693228847..9e3d6a6accb 100644 --- a/.pipelines/templates/release-upload-buildinfo.yml +++ b/.pipelines/templates/release-upload-buildinfo.yml @@ -14,11 +14,9 @@ jobs: demands: - ImageOverride -equals PSMMS2019-Secure variables: - - name: runCodesignValidationInjection - value: false - name: NugetSecurityAnalysisWarningLevel value: none - - name: DOTNET_SKIP_FIRST_TIME_EXPERIENCE + - name: DOTNET_NOLOGO value: 1 - name: ob_outputDirectory value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' @@ -53,13 +51,19 @@ jobs: Import-Module "$toolsDirectory/ci.psm1" $jsonFile = Get-Item "$ENV:PIPELINE_WORKSPACE/PSPackagesOfficial/BuildInfoJson/*.json" $fileName = Split-Path $jsonFile -Leaf + # The build itself has already determined if it is preview or stable/LTS, + # we just need to check via the file name + $isPreview = $fileName -eq "preview.json" + $isStable = $fileName -eq "stable.json" $dateTime = [datetime]::UtcNow $dateTime = [datetime]::new($dateTime.Ticks - ($dateTime.Ticks % [timespan]::TicksPerSecond), $dateTime.Kind) $metadata = Get-Content -LiteralPath "$toolsDirectory/metadata.json" -ErrorAction Stop | ConvertFrom-Json - $stableRelease = $metadata.StableRelease.Latest - $ltsRelease = $metadata.LTSRelease.Latest + # Note: version tags in metadata.json (e.g. StableReleaseTag) may not reflect the current release being + # published, so they must not be used to gate channel decisions. Use the explicit publish flags instead. + $stableRelease = $metadata.StableRelease.PublishToChannels + $ltsRelease = $metadata.LTSRelease.PublishToChannels Write-Verbose -Verbose "Writing $jsonFile contents:" $buildInfoJsonContent = Get-Content $jsonFile -Encoding UTF8NoBom -Raw @@ -67,40 +71,48 @@ jobs: $buildInfo = $buildInfoJsonContent | ConvertFrom-Json $buildInfo.ReleaseDate = $dateTime + $currentReleaseTag = $buildInfo.ReleaseTag -Replace 'v','' $targetFile = "$ENV:PIPELINE_WORKSPACE/$fileName" ConvertTo-Json -InputObject $buildInfo | Out-File $targetFile -Encoding ascii - if ($stableRelease -or $fileName -eq "preview.json") { - Set-BuildVariable -Name CopyMainBuildInfo -Value YES + if ($isPreview) { + Set-BuildVariable -Name UploadPreview -Value YES } else { - Set-BuildVariable -Name CopyMainBuildInfo -Value NO + Set-BuildVariable -Name UploadPreview -Value NO } - Set-BuildVariable -Name BuildInfoJsonFile -Value $targetFile - - ## Create 'lts.json' if it's the latest stable and also a LTS release. + Set-BuildVariable -Name PreviewBuildInfoFile -Value $targetFile - if ($fileName -eq "stable.json") { + ## Create 'lts.json' if marked as a LTS release. + if ($isStable) { if ($ltsRelease) { $ltsFile = "$ENV:PIPELINE_WORKSPACE/lts.json" Copy-Item -Path $targetFile -Destination $ltsFile -Force - Set-BuildVariable -Name LtsBuildInfoJsonFile -Value $ltsFile - Set-BuildVariable -Name CopyLTSBuildInfo -Value YES + Set-BuildVariable -Name LTSBuildInfoFile -Value $ltsFile + Set-BuildVariable -Name UploadLTS -Value YES } else { - Set-BuildVariable -Name CopyLTSBuildInfo -Value NO + Set-BuildVariable -Name UploadLTS -Value NO } - $releaseTag = $buildInfo.ReleaseTag - $version = $releaseTag -replace '^v' - $semVersion = [System.Management.Automation.SemanticVersion] $version + ## Gate stable.json upload on the metadata publish flag. + if ($stableRelease) { + Set-BuildVariable -Name StableBuildInfoFile -Value $targetFile + Set-BuildVariable -Name UploadStable -Value YES + } else { + Set-BuildVariable -Name UploadStable -Value NO + } - $versionFile = "$ENV:PIPELINE_WORKSPACE/$($semVersion.Major)-$($semVersion.Minor).json" + ## Always publish the version-specific {Major}-{Minor}.json for non-preview builds. + [System.Management.Automation.SemanticVersion] $currentVersion = $currentReleaseTag + $versionFile = "$ENV:PIPELINE_WORKSPACE/$($currentVersion.Major)-$($currentVersion.Minor).json" Copy-Item -Path $targetFile -Destination $versionFile -Force - Set-BuildVariable -Name VersionBuildInfoJsonFile -Value $versionFile - Set-BuildVariable -Name CopyVersionBuildInfo -Value YES + Set-BuildVariable -Name VersionSpecificBuildInfoFile -Value $versionFile + Set-BuildVariable -Name UploadVersionSpecific -Value YES + } else { - Set-BuildVariable -Name CopyVersionBuildInfo -Value NO + Set-BuildVariable -Name UploadStable -Value NO + Set-BuildVariable -Name UploadVersionSpecific -Value NO } displayName: Create json files @@ -118,24 +130,35 @@ jobs: $storageContext = New-AzStorageContext -StorageAccountName $storageAccount -UseConnectedAccount - if ($env:CopyMainBuildInfo -eq 'YES') { - $jsonFile = "$env:BuildInfoJsonFile" + #preview + if ($env:UploadPreview -eq 'YES') { + $jsonFile = "$env:PreviewBuildInfoFile" + $blobName = Get-Item $jsonFile | Split-Path -Leaf + Write-Verbose -Verbose "Uploading $jsonFile to $containerName/$prefix/$blobName" + Set-AzStorageBlobContent -File $jsonFile -Container $containerName -Blob "$prefix/$blobName" -Context $storageContext -Force + } + + #LTS + if ($env:UploadLTS -eq 'YES') { + $jsonFile = "$env:LTSBuildInfoFile" $blobName = Get-Item $jsonFile | Split-Path -Leaf Write-Verbose -Verbose "Uploading $jsonFile to $containerName/$prefix/$blobName" Set-AzStorageBlobContent -File $jsonFile -Container $containerName -Blob "$prefix/$blobName" -Context $storageContext -Force } - if ($env:CopyLTSBuildInfo -eq 'YES') { - $jsonFile = "$env:LtsBuildInfoJsonFile" + #stable + if ($env:UploadStable -eq 'YES') { + $jsonFile = "$env:StableBuildInfoFile" $blobName = Get-Item $jsonFile | Split-Path -Leaf Write-Verbose -Verbose "Uploading $jsonFile to $containerName/$prefix/$blobName" Set-AzStorageBlobContent -File $jsonFile -Container $containerName -Blob "$prefix/$blobName" -Context $storageContext -Force } - if ($env:CopyVersionBuildInfo -eq 'YES') { - $jsonFile = "$env:VersionBuildInfoJsonFile" + #version-specific + if ($env:UploadVersionSpecific -eq 'YES') { + $jsonFile = "$env:VersionSpecificBuildInfoFile" $blobName = Get-Item $jsonFile | Split-Path -Leaf Write-Verbose -Verbose "Uploading $jsonFile to $containerName/$prefix/$blobName" Set-AzStorageBlobContent -File $jsonFile -Container $containerName -Blob "$prefix/$blobName" -Context $storageContext -Force } - condition: and(succeeded(), eq(variables['CopyMainBuildInfo'], 'YES')) + condition: and(succeeded(), or(eq(variables['UploadPreview'], 'YES'), eq(variables['UploadLTS'], 'YES'), eq(variables['UploadStable'], 'YES'), eq(variables['UploadVersionSpecific'], 'YES'))) diff --git a/.pipelines/templates/release-validate-fxdpackages.yml b/.pipelines/templates/release-validate-fxdpackages.yml index 30a2ab13905..3f4f9a3bb6c 100644 --- a/.pipelines/templates/release-validate-fxdpackages.yml +++ b/.pipelines/templates/release-validate-fxdpackages.yml @@ -61,12 +61,7 @@ jobs: Get-ChildItem "$(Pipeline.Workspace)/PSPackagesOfficial/$artifactName" -Recurse displayName: 'Capture Downloaded Artifacts' - - task: UseDotNet@2 - displayName: 'Use .NET Core sdk' - inputs: - useGlobalJson: true - packageType: 'sdk' - workingDirectory: $(Build.SourcesDirectory)/PowerShell" + - template: /.pipelines/templates/install-dotnet.yml@self - pwsh: | $artifactName = '$(artifactName)' @@ -98,7 +93,7 @@ jobs: $artifactName = '$(artifactName)' $rootPath = "$(Pipeline.Workspace)/PSPackagesOfficial/$artifactName" - $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 + $env:DOTNET_NOLOGO=1 Import-Module "$repoRoot/build.psm1" -Force Find-Dotnet -SetDotnetRoot Write-Verbose -Verbose "DOTNET_ROOT: $env:DOTNET_ROOT" diff --git a/.pipelines/templates/release-validate-globaltools.yml b/.pipelines/templates/release-validate-globaltools.yml index 3dd1fd15f56..8c2031d5cc9 100644 --- a/.pipelines/templates/release-validate-globaltools.yml +++ b/.pipelines/templates/release-validate-globaltools.yml @@ -38,16 +38,14 @@ jobs: Get-ChildItem "$(Pipeline.Workspace)/PSPackagesOfficial/drop_nupkg_build_nupkg" -Recurse displayName: 'Capture Downloaded Artifacts' - - task: UseDotNet@2 - displayName: 'Use .NET Core sdk' - inputs: - useGlobalJson: true - packageType: 'sdk' - workingDirectory: $(REPOROOT) + - template: /.pipelines/templates/install-dotnet.yml@self - pwsh: | $repoRoot = "$(Build.SourcesDirectory)/PowerShell" + Import-Module "$repoRoot/build.psm1" -Force -Verbose + Start-PSBootstrap -Scenario Dotnet + $toolPath = New-Item -ItemType Directory "$(System.DefaultWorkingDirectory)/toolPath" | Select-Object -ExpandProperty FullName Write-Verbose -Verbose "dotnet tool list -g" @@ -80,6 +78,9 @@ jobs: - pwsh: | $repoRoot = "$(Build.SourcesDirectory)/PowerShell" + Import-Module "$repoRoot/build.psm1" -Force -Verbose + Start-PSBootstrap -Scenario Dotnet + $exeName = if ($IsWindows) { "pwsh.exe" } else { "pwsh" } $toolPath = "$(System.DefaultWorkingDirectory)/toolPath/${{ parameters.globalToolExeName }}" diff --git a/.pipelines/templates/release-validate-packagenames.yml b/.pipelines/templates/release-validate-packagenames.yml index c717b50f289..5953366ffd7 100644 --- a/.pipelines/templates/release-validate-packagenames.yml +++ b/.pipelines/templates/release-validate-packagenames.yml @@ -82,7 +82,7 @@ jobs: - pwsh: | $message = @() Get-ChildItem $(System.ArtifactsDirectory)\* -recurse -filter *.pkg | ForEach-Object { - if($_.Name -notmatch 'powershell-(lts-)?\d+\.\d+\.\d+\-([a-z]*.\d+\-)?osx(\.10\.12)?\-(x64|arm64)\.pkg') + if($_.Name -notmatch 'powershell-(lts-)?\d+\.\d+\.\d+\-([a-z]*.\d+\-)?osx\-(x64|arm64)\.pkg') { $messageInstance = "$($_.Name) is not a valid package name" $message += $messageInstance @@ -94,8 +94,8 @@ jobs: - pwsh: | $message = @() - Get-ChildItem $(System.ArtifactsDirectory)\* -recurse -include *.zip, *.msi | ForEach-Object { - if($_.Name -notmatch 'PowerShell-\d+\.\d+\.\d+\-([a-z]*.\d+\-)?win\-(fxdependent|x64|arm64|x86|fxdependentWinDesktop)\.(msi|zip){1}') + Get-ChildItem $(System.ArtifactsDirectory)\* -recurse -include *.zip | ForEach-Object { + if($_.Name -notmatch 'PowerShell-\d+\.\d+\.\d+\-([a-z]*.\d+\-)?win\-(fxdependent|x64|arm64|x86|fxdependentWinDesktop)\.(zip){1}') { $messageInstance = "$($_.Name) is not a valid package name" $message += $messageInstance @@ -104,12 +104,12 @@ jobs: } if($message.count -gt 0){throw ($message | out-string)} - displayName: Validate Zip and MSI Package Names + displayName: Validate Zip Package Names - pwsh: | $message = @() Get-ChildItem $(System.ArtifactsDirectory)\* -recurse -filter *.deb | ForEach-Object { - if($_.Name -notmatch 'powershell(-preview|-lts)?_\d+\.\d+\.\d+([\-~][a-z]*.\d+)?-\d\.deb_amd64\.deb') + if($_.Name -notmatch 'powershell(-preview|-lts)?_\d+\.\d+\.\d+([\-~][a-z]*.\d+)?-\d\.deb_(amd64|arm64)\.deb') { $messageInstance = "$($_.Name) is not a valid package name" $message += $messageInstance diff --git a/.pipelines/templates/release-validate-sdk.yml b/.pipelines/templates/release-validate-sdk.yml index 6eb800f6326..3b0442f65d6 100644 --- a/.pipelines/templates/release-validate-sdk.yml +++ b/.pipelines/templates/release-validate-sdk.yml @@ -46,17 +46,15 @@ jobs: Get-ChildItem "$(Pipeline.Workspace)/PSPackagesOfficial/drop_nupkg_build_nupkg" -Recurse displayName: 'Capture Downloaded Artifacts' - - task: UseDotNet@2 - displayName: 'Use .NET Core sdk' - inputs: - useGlobalJson: true - packageType: 'sdk' - workingDirectory: $(REPOROOT) + - template: /.pipelines/templates/install-dotnet.yml@self - pwsh: | $repoRoot = "$(Build.SourcesDirectory)" - $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 + Import-Module "$repoRoot/build.psm1" -Force -Verbose + Start-PSBootstrap -Scenario Dotnet + + $env:DOTNET_NOLOGO=1 $localLocation = "$(Pipeline.Workspace)/PSPackagesOfficial/drop_nupkg_build_nupkg" $xmlElement = @" diff --git a/.pipelines/templates/set-reporoot.yml b/.pipelines/templates/set-reporoot.yml new file mode 100644 index 00000000000..af7983afaa1 --- /dev/null +++ b/.pipelines/templates/set-reporoot.yml @@ -0,0 +1,35 @@ +parameters: +- name: ob_restore_phase + type: boolean + default: true + +steps: +- pwsh: | + $path = "./build.psm1" + if($env:REPOROOT){ + Write-Verbose "reporoot already set to ${env:REPOROOT}" -Verbose + exit 0 + } + if(Test-Path -Path $path) + { + Write-Verbose "reporoot detected at: ." -Verbose + $repoRoot = '.' + } + else{ + $path = "./PowerShell/build.psm1" + if(Test-Path -Path $path) + { + Write-Verbose "reporoot detected at: ./PowerShell" -Verbose + $repoRoot = './PowerShell' + } + } + if($repoRoot) { + $vstsCommandString = "vso[task.setvariable variable=repoRoot]$repoRoot" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + } else { + Write-Verbose -Verbose "repo not found" + } + displayName: 'Set repo Root' + env: + ob_restore_phase: ${{ parameters.ob_restore_phase }} diff --git a/.pipelines/templates/shouldSign.yml b/.pipelines/templates/shouldSign.yml index 4bac9e1a3ae..f3701acbc97 100644 --- a/.pipelines/templates/shouldSign.yml +++ b/.pipelines/templates/shouldSign.yml @@ -1,11 +1,16 @@ +parameters: +- name: ob_restore_phase + type: boolean + default: true + steps: - powershell: | $shouldSign = $true - $authenticodeCert = 'CP-230012' - $msixCert = 'CP-230012' + $authenticodeCert = '$(authenticode_cert_id)' + $msixCert = '$(authenticode_cert_id)' if($env:IS_DAILY -eq 'true') { - $authenticodeCert = 'CP-460906' + $authenticodeCert = '$(authenticode_test_cert_id)' } if($env:SKIP_SIGNING -eq 'Yes') { @@ -22,4 +27,4 @@ steps: Write-Host "##$vstsCommandString" displayName: 'Set SHOULD_SIGN Variable' env: - ob_restore_phase: true # This ensures this done in restore phase to workaround signing issue + ob_restore_phase: ${{ parameters.ob_restore_phase }} diff --git a/.pipelines/templates/stages/PowerShell-Coordinated_Packages-Stages.yml b/.pipelines/templates/stages/PowerShell-Coordinated_Packages-Stages.yml new file mode 100644 index 00000000000..cd0a4ebc065 --- /dev/null +++ b/.pipelines/templates/stages/PowerShell-Coordinated_Packages-Stages.yml @@ -0,0 +1,202 @@ +parameters: + - name: RUN_WINDOWS + type: boolean + default: true + - name: RUN_TEST_AND_RELEASE + type: boolean + default: true + - name: OfficialBuild + type: boolean + +stages: +- stage: prep + jobs: + - job: SetVars + displayName: Set Variables + pool: + type: linux + + variables: + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT/BuildJson' + - name: ob_sdl_codeSignValidation_enabled + value: false + - name: ob_sdl_codeql_compiled_enabled + value: false + - name: ob_sdl_credscan_suppressionsFile + value: $(Build.SourcesDirectory)\PowerShell\.config\suppress.json + - name: ob_sdl_tsa_configFile + value: $(Build.SourcesDirectory)\PowerShell\.config\tsaoptions.json + - name: ob_signing_setup_enabled + value: false + - name: ob_sdl_sbom_enabled + value: false + + steps: + - checkout: self + clean: true + env: + ob_restore_phase: true # This ensures checkout is done at the beginning of the restore phase + + - pwsh: | + Get-ChildItem Env: | Out-String -width 9999 -Stream | write-Verbose -Verbose + displayName: Capture environment variables + env: + ob_restore_phase: true # This ensures checkout is done at the beginning of the restore phase + + - template: /.pipelines/templates/SetVersionVariables.yml@self + parameters: + ReleaseTagVar: $(ReleaseTagVar) + CreateJson: yes + +- stage: macos + displayName: macOS - build and sign + dependsOn: ['prep'] + variables: + - name: ps_official_build + value: ${{ parameters.OfficialBuild }} + jobs: + - template: /.pipelines/templates/mac.yml@self + parameters: + buildArchitecture: x64 + - template: /.pipelines/templates/mac.yml@self + parameters: + buildArchitecture: arm64 + +- stage: linux + displayName: linux - build and sign + dependsOn: ['prep'] + variables: + - name: ps_official_build + value: ${{ parameters.OfficialBuild }} + jobs: + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'linux-x64' + JobName: 'linux_x64' + + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'linux-x64' + JobName: 'linux_x64_minSize' + BuildConfiguration: 'minSize' + + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'linux-arm' + JobName: 'linux_arm' + + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'linux-arm64' + JobName: 'linux_arm64' + + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'fxdependent-linux-x64' + JobName: 'linux_fxd_x64_mariner' + + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'fxdependent-linux-arm64' + JobName: 'linux_fxd_arm64_mariner' + + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'fxdependent-noopt-linux-musl-x64' + JobName: 'linux_fxd_x64_alpine' + + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'fxdependent' + JobName: 'linux_fxd' + + - template: /.pipelines/templates/linux.yml@self + parameters: + Runtime: 'linux-musl-x64' + JobName: 'linux_x64_alpine' + +- stage: windows + displayName: windows - build and sign + dependsOn: ['prep'] + condition: and(succeeded(),eq('${{ parameters.RUN_WINDOWS }}','true')) + variables: + - name: ps_official_build + value: ${{ parameters.OfficialBuild }} + jobs: + - template: /.pipelines/templates/windows-hosted-build.yml@self + parameters: + Architecture: x64 + BuildConfiguration: release + JobName: build_windows_x64_release + - template: /.pipelines/templates/windows-hosted-build.yml@self + parameters: + Architecture: x64 + BuildConfiguration: minSize + JobName: build_windows_x64_minSize_release + - template: /.pipelines/templates/windows-hosted-build.yml@self + parameters: + Architecture: x86 + JobName: build_windows_x86_release + - template: /.pipelines/templates/windows-hosted-build.yml@self + parameters: + Architecture: arm64 + JobName: build_windows_arm64_release + - template: /.pipelines/templates/windows-hosted-build.yml@self + parameters: + Architecture: fxdependent + JobName: build_windows_fxdependent_release + - template: /.pipelines/templates/windows-hosted-build.yml@self + parameters: + Architecture: fxdependentWinDesktop + JobName: build_windows_fxdependentWinDesktop_release + +- stage: test_and_release_artifacts + displayName: Test and Release Artifacts + dependsOn: ['prep'] + condition: and(succeeded(),eq('${{ parameters.RUN_TEST_AND_RELEASE }}','true')) + jobs: + - template: /.pipelines/templates/testartifacts.yml@self + + - job: release_json + displayName: Create and Upload release.json + pool: + type: windows + variables: + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' + - name: ob_sdl_tsa_configFile + value: $(Build.SourcesDirectory)\PowerShell\.config\tsaoptions.json + - name: ob_sdl_credscan_suppressionsFile + value: $(Build.SourcesDirectory)\PowerShell\.config\suppress.json + steps: + - checkout: self + clean: true + - template: /.pipelines/templates/SetVersionVariables.yml@self + parameters: + ReleaseTagVar: $(ReleaseTagVar) + - template: /.pipelines/templates/rebuild-branch-check.yml@self + - powershell: | + $metadata = Get-Content '$(Build.SourcesDirectory)/PowerShell/tools/metadata.json' -Raw | ConvertFrom-Json + + # Use the rebuild branch check from the template + $isRebuildBranch = '$(RebuildBranchCheck.IsRebuildBranch)' -eq 'true' + + # Don't mark as LTS release for rebuild branches + $LTS = $metadata.LTSRelease.Package -and -not $isRebuildBranch + + if ($isRebuildBranch) { + Write-Verbose -Message "Rebuild branch detected, not marking as LTS release" -Verbose + } + + @{ ReleaseVersion = "$(Version)"; LTSRelease = $LTS } | ConvertTo-Json | Out-File "$(Build.StagingDirectory)\release.json" + Get-Content "$(Build.StagingDirectory)\release.json" + + if (-not (Test-Path "$(ob_outputDirectory)\metadata")) { + New-Item -ItemType Directory -Path "$(ob_outputDirectory)\metadata" + } + + Copy-Item -Path "$(Build.StagingDirectory)\release.json" -Destination "$(ob_outputDirectory)\metadata" -Force + displayName: Create and upload release.json file to build artifact + retryCountOnTaskFailure: 2 + - template: /.pipelines/templates/step/finalize.yml@self diff --git a/.pipelines/templates/stages/PowerShell-Packages-Stages.yml b/.pipelines/templates/stages/PowerShell-Packages-Stages.yml new file mode 100644 index 00000000000..399d242aa4b --- /dev/null +++ b/.pipelines/templates/stages/PowerShell-Packages-Stages.yml @@ -0,0 +1,197 @@ +parameters: + - name: OfficialBuild + type: boolean + +stages: +- stage: prep + displayName: 'Prep BuildInfo+Az' + jobs: + - template: /.pipelines/templates/checkAzureContainer.yml@self + +- stage: mac_package + displayName: 'macOS Pkg+Sign' + dependsOn: [] + jobs: + - template: /.pipelines/templates/mac-package-build.yml@self + parameters: + buildArchitecture: x64 + + - template: /.pipelines/templates/mac-package-build.yml@self + parameters: + buildArchitecture: arm64 + +- stage: windows_package_build + displayName: 'Win Pkg (unsigned)' + dependsOn: [] + jobs: + - template: /.pipelines/templates/packaging/windows/package.yml@self + parameters: + runtime: x64 + + - template: /.pipelines/templates/packaging/windows/package.yml@self + parameters: + runtime: arm64 + + - template: /.pipelines/templates/packaging/windows/package.yml@self + parameters: + runtime: x86 + + - template: /.pipelines/templates/packaging/windows/package.yml@self + parameters: + runtime: fxdependent + + - template: /.pipelines/templates/packaging/windows/package.yml@self + parameters: + runtime: fxdependentWinDesktop + + - template: /.pipelines/templates/packaging/windows/package.yml@self + parameters: + runtime: minsize + +- stage: windows_package_sign + displayName: 'Win Pkg Sign' + dependsOn: [windows_package_build] + jobs: + - template: /.pipelines/templates/packaging/windows/sign.yml@self + parameters: + runtime: x64 + + - template: /.pipelines/templates/packaging/windows/sign.yml@self + parameters: + runtime: arm64 + + - template: /.pipelines/templates/packaging/windows/sign.yml@self + parameters: + runtime: x86 + + - template: /.pipelines/templates/packaging/windows/sign.yml@self + parameters: + runtime: fxdependent + + - template: /.pipelines/templates/packaging/windows/sign.yml@self + parameters: + runtime: fxdependentWinDesktop + + - template: /.pipelines/templates/packaging/windows/sign.yml@self + parameters: + runtime: minsize + +- stage: linux_package + displayName: 'Linux Pkg+Sign' + dependsOn: [] + jobs: + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_x64' + signedDrop: 'drop_linux_sign_linux_x64' + packageType: deb + jobName: deb + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_arm64' + signedDrop: 'drop_linux_sign_linux_arm64' + packageType: deb-arm64 + jobName: deb_arm64 + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_fxd_x64_mariner' + signedDrop: 'drop_linux_sign_linux_fxd_x64_mariner' + packageType: rpm-fxdependent #mariner-x64 + jobName: mariner_x64 + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_fxd_arm64_mariner' + signedDrop: 'drop_linux_sign_linux_fxd_arm64_mariner' + packageType: rpm-fxdependent-arm64 #mariner-arm64 + jobName: mariner_arm64 + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_x64' + signedDrop: 'drop_linux_sign_linux_x64' + packageType: rpm + jobName: rpm + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_arm' + signedDrop: 'drop_linux_sign_linux_arm' + packageType: tar-arm + jobName: tar_arm + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_arm64' + signedDrop: 'drop_linux_sign_linux_arm64' + packageType: tar-arm64 + jobName: tar_arm64 + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_x64_alpine' + signedDrop: 'drop_linux_sign_linux_x64_alpine' + packageType: tar-alpine + jobName: tar_alpine + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_fxd' + signedDrop: 'drop_linux_sign_linux_fxd' + packageType: fxdependent + jobName: fxdependent + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_x64' + signedDrop: 'drop_linux_sign_linux_x64' + packageType: tar + jobName: tar + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_fxd_x64_alpine' + signedDrop: 'drop_linux_sign_linux_fxd_x64_alpine' + packageType: tar-alpine-fxdependent + jobName: tar_alpine_fxd + + - template: /.pipelines/templates/linux-package-build.yml@self + parameters: + unsignedDrop: 'drop_linux_build_linux_x64_minSize' + signedDrop: 'drop_linux_sign_linux_x64_minSize' + packageType: min-size + jobName: minSize + +- stage: nupkg + displayName: 'NuGet Pkg+Sign' + dependsOn: [] + jobs: + - template: /.pipelines/templates/nupkg.yml@self + +- stage: msixbundle + displayName: 'MSIX Bundle+Sign' + dependsOn: [windows_package_build] # Only depends on unsigned packages + jobs: + - template: /.pipelines/templates/package-create-msix.yml@self + parameters: + OfficialBuild: ${{ parameters.OfficialBuild }} + +- stage: store_package + displayName: 'Store Package' + dependsOn: [msixbundle] + jobs: + - template: /.pipelines/templates/package-store-package.yml@self + +- stage: upload + displayName: 'Upload' + dependsOn: [prep, mac_package, windows_package_sign, linux_package, nupkg, msixbundle] # prep needed for BuildInfo JSON + jobs: + - template: /.pipelines/templates/uploadToAzure.yml@self + +- stage: validatePackages + displayName: 'Validate Packages' + dependsOn: [upload] + jobs: + - template: /.pipelines/templates/release-validate-packagenames.yml@self diff --git a/.pipelines/templates/stages/PowerShell-Release-Stages.yml b/.pipelines/templates/stages/PowerShell-Release-Stages.yml new file mode 100644 index 00000000000..52ce428a663 --- /dev/null +++ b/.pipelines/templates/stages/PowerShell-Release-Stages.yml @@ -0,0 +1,323 @@ +parameters: + - name: releaseEnvironment + type: string + - name: SkipPublish + type: boolean + - name: SkipPSInfraInstallers + type: boolean + - name: skipMSIXPublish + type: boolean + +stages: +- stage: setReleaseTagAndChangelog + displayName: 'Set Release Tag and Upload Changelog' + jobs: + - template: /.pipelines/templates/release-SetTagAndChangelog.yml@self + +- stage: validateSdk + displayName: 'Validate SDK' + dependsOn: [] + jobs: + - template: /.pipelines/templates/release-validate-sdk.yml@self + parameters: + jobName: "windowsSDK" + displayName: "Windows SDK Validation" + imageName: PSMMS2019-Secure + poolName: $(windowsPool) + + - template: /.pipelines/templates/release-validate-sdk.yml@self + parameters: + jobName: "MacOSSDK" + displayName: "MacOS SDK Validation" + imageName: macOS-latest + poolName: Azure Pipelines + + - template: /.pipelines/templates/release-validate-sdk.yml@self + parameters: + jobName: "LinuxSDK" + displayName: "Linux SDK Validation" + imageName: PSMMSUbuntu22.04-Secure + poolName: $(ubuntuPool) + +- stage: gbltool + displayName: 'Validate Global tools' + dependsOn: [] + jobs: + - template: /.pipelines/templates/release-validate-globaltools.yml@self + parameters: + jobName: "WindowsGlobalTools" + displayName: "Windows Global Tools Validation" + jobtype: windows + + - template: /.pipelines/templates/release-validate-globaltools.yml@self + parameters: + jobName: "LinuxGlobalTools" + displayName: "Linux Global Tools Validation" + jobtype: linux + globalToolExeName: 'pwsh' + globalToolPackageName: 'PowerShell.Linux.x64' + +- stage: fxdpackages + displayName: 'Validate FXD Packages' + dependsOn: [] + jobs: + - template: /.pipelines/templates/release-validate-fxdpackages.yml@self + parameters: + jobName: 'winfxd' + displayName: 'Validate Win Fxd Packages' + jobtype: 'windows' + artifactName: 'drop_windows_package_package_win_fxdependent' + packageNamePattern: '**/*win-fxdependent.zip' + + - template: /.pipelines/templates/release-validate-fxdpackages.yml@self + parameters: + jobName: 'winfxdDesktop' + displayName: 'Validate WinDesktop Fxd Packages' + jobtype: 'windows' + artifactName: 'drop_windows_package_package_win_fxdependentWinDesktop' + packageNamePattern: '**/*win-fxdependentwinDesktop.zip' + + - template: /.pipelines/templates/release-validate-fxdpackages.yml@self + parameters: + jobName: 'linuxfxd' + displayName: 'Validate Linux Fxd Packages' + jobtype: 'linux' + artifactName: 'drop_linux_package_fxdependent' + packageNamePattern: '**/*linux-x64-fxdependent.tar.gz' + + - template: /.pipelines/templates/release-validate-fxdpackages.yml@self + parameters: + jobName: 'linuxArm64fxd' + displayName: 'Validate Linux ARM64 Fxd Packages' + jobtype: 'linux' + artifactName: 'drop_linux_package_fxdependent' + # this is really an architecture independent package + packageNamePattern: '**/*linux-x64-fxdependent.tar.gz' + arm64: 'yes' + enableCredScan: false + +- stage: ManualValidation + dependsOn: [] + displayName: Manual Validation + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Validate Windows Packages + jobName: ValidateWinPkg + instructions: | + Validate zip package on windows + + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Validate OSX Packages + jobName: ValidateOsxPkg + instructions: | + Validate tar.gz package on osx-arm64 + +- stage: ReleaseAutomation + dependsOn: [] + displayName: 'Release Automation' + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Start Release Automation + jobName: StartRA + instructions: | + Kick off Release automation build at: https://dev.azure.com/powershell-rel/Release-Automation/_build?definitionId=10&_a=summary + + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Triage results + jobName: TriageRA + dependsOnJob: StartRA + instructions: | + Triage ReleaseAutomation results + + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Signoff Tests + dependsOnJob: TriageRA + jobName: SignoffTests + instructions: | + Signoff ReleaseAutomation results + +- stage: UpdateChangeLog + displayName: Update the changelog + dependsOn: + - ManualValidation + - ReleaseAutomation + - fxdpackages + - gbltool + - validateSdk + + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Make sure the changelog is updated + jobName: MergeChangeLog + instructions: | + Update and merge the changelog for the release. + This step is required for creating GitHub draft release. + +- stage: PublishGitHubReleaseAndNuget + displayName: Publish GitHub and Nuget Release + dependsOn: + - setReleaseTagAndChangelog + - UpdateChangeLog + variables: + ob_release_environment: ${{ parameters.releaseEnvironment }} + jobs: + - template: /.pipelines/templates/release-githubNuget.yml@self + parameters: + skipPublish: ${{ parameters.SkipPublish }} + +- stage: PushGitTagAndMakeDraftPublic + displayName: Push Git Tag and Make Draft Public + dependsOn: PublishGitHubReleaseAndNuget + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Push Git Tag + jobName: PushGitTag + instructions: | + Push the git tag to upstream + + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Make Draft Public + dependsOnJob: PushGitTag + jobName: DraftPublic + instructions: | + Make the GitHub Release Draft Public + +- stage: BlobPublic + displayName: Make Blob Public + dependsOn: + - UpdateChangeLog + - PushGitTagAndMakeDraftPublic + jobs: + - template: /.pipelines/templates/release-MakeBlobPublic.yml@self + parameters: + SkipPSInfraInstallers: ${{ parameters.SkipPSInfraInstallers }} + +- stage: PublishPMC + displayName: Publish PMC + dependsOn: PushGitTagAndMakeDraftPublic + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Publish to PMC + jobName: ReleaseToPMC + instructions: | + Run PowerShell-Release-Official-Azure.yml pipeline to publish to PMC + +- stage: UpdateDotnetDocker + dependsOn: PushGitTagAndMakeDraftPublic + displayName: Update DotNet SDK Docker images + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Update .NET SDK docker images + jobName: DotnetDocker + instructions: | + Create PR for updating dotnet-docker images to use latest PowerShell version. + 1. Fork and clone https://github.com/dotnet/dotnet-docker.git + 2. git checkout upstream/nightly -b updatePS + 3. dotnet run --project .\eng\update-dependencies\ specific --product-version powershell= --compute-shas + 4. create PR targeting nightly branch + +- stage: UpdateWinGet + dependsOn: PushGitTagAndMakeDraftPublic + displayName: Add manifest entry to winget + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Add manifest entry to winget + jobName: UpdateWinGet + instructions: | + This is typically done by the community 1-2 days after the release. + +- stage: PublishMsix + dependsOn: + - setReleaseTagAndChangelog + - PushGitTagAndMakeDraftPublic + displayName: Publish MSIX to store + variables: + ob_release_environment: ${{ parameters.releaseEnvironment }} + jobs: + - template: /.pipelines/templates/release-MSIX-Publish.yml@self + parameters: + skipMSIXPublish: ${{ parameters.skipMSIXPublish }} + +- stage: PublishVPack + dependsOn: PushGitTagAndMakeDraftPublic + displayName: Release vPack + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Start 2 vPack Release pipelines + jobName: PublishVPack + instructions: | + 1. Kick off PowerShell-vPack-Official pipeline + 2. Kick off PowerShell-MSIXBundle-VPack pipeline + +# Need to verify if the Az PS / CLI team still uses this. Skipping for this release. +# - stage: ReleaseDeps +# dependsOn: GitHubTasks +# displayName: Update pwsh.deps.json links +# jobs: +# - template: templates/release-UpdateDepsJson.yml + +- stage: UploadBuildInfoJson + dependsOn: PushGitTagAndMakeDraftPublic + displayName: Upload BuildInfo.json + jobs: + - template: /.pipelines/templates/release-upload-buildinfo.yml@self + +- stage: ReleaseSymbols + dependsOn: PushGitTagAndMakeDraftPublic + displayName: Release Symbols + jobs: + - template: /.pipelines/templates/release-symbols.yml@self + +- stage: ChangesToMaster + displayName: Ensure changes are in GH master + dependsOn: + - PublishPMC + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Make sure changes are in master + jobName: MergeToMaster + instructions: | + Make sure that changes README.md and metadata.json are merged into master on GitHub. + +- stage: ReleaseToMU + displayName: Release to MU + dependsOn: PushGitTagAndMakeDraftPublic # This only needs the blob to be available + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Release to MU + instructions: | + Notify the PM team to start the process of releasing to MU. + +- stage: ReleaseClose + displayName: Finish Release + dependsOn: + - ReleaseToMU + - ReleaseSymbols + jobs: + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Retain Build + jobName: RetainBuild + instructions: | + Retain the build + + - template: /.pipelines/templates/approvalJob.yml@self + parameters: + displayName: Delete release branch + jobName: DeleteBranch + instructions: | + Delete release branch diff --git a/.pipelines/templates/stages/PowerShell-vPack-Stages.yml b/.pipelines/templates/stages/PowerShell-vPack-Stages.yml new file mode 100644 index 00000000000..01a83a5b161 --- /dev/null +++ b/.pipelines/templates/stages/PowerShell-vPack-Stages.yml @@ -0,0 +1,236 @@ +parameters: + - name: createVPack + type: boolean + - name: vPackName + type: string + +stages: +- stage: BuildStage + jobs: + - job: BuildJob + pool: + type: windows + + strategy: + matrix: + x86: + architecture: x86 + + x64: + architecture: x64 + + arm64: + architecture: arm64 + + variables: + ArtifactPlatform: 'windows' + ob_artifactBaseName: drop_build_$(architecture) + ob_outputDirectory: '$(BUILD.SOURCESDIRECTORY)\out' + ob_createvpack_enabled: ${{ parameters.createVPack }} + ob_createvpack_owneralias: tplunk + ob_createvpack_versionAs: parts + ob_createvpack_propsFile: true + ob_createvpack_verbose: true + ob_createvpack_packagename: '${{ parameters.vPackName }}.$(architecture)' + ob_createvpack_description: PowerShell $(architecture) $(version) + # I think the variables reload after we transition back to the host so this works. 🤷‍♂️ + ob_createvpack_majorVer: $(pwshMajorVersion) + ob_createvpack_minorVer: $(pwshMinorVersion) + ob_createvpack_patchVer: $(pwshPatchVersion) + ${{ if ne(variables['pwshPrereleaseVersion'], '') }}: + ob_createvpack_prereleaseVer: $(pwshPrereleaseVersion) + ${{ else }}: + ob_createvpack_prereleaseVer: $(Build.SourceVersion) + + steps: + - checkout: self + displayName: Checkout source code - during restore + clean: true + path: s + env: + ob_restore_phase: true + + - template: /.pipelines/templates/SetVersionVariables.yml@self + parameters: + ReleaseTagVar: $(ReleaseTagVar) + CreateJson: yes + + - pwsh: | + $version = '$(Version)' + Write-Verbose -Verbose "Version: $version" + if(!$version) { + throw "Version is not set." + } + + $mainVersionParts = $version -split '-' + + Write-Verbose -Verbose "mainVersionParts: $($mainVersionParts[0]) ; $($mainVersionParts[1])" + $versionParts = $mainVersionParts[0] -split '[.]'; + $major = $versionParts[0] + $minor = $versionParts[1] + $patch = $versionParts[2] + + $previewPart = $mainVersionParts[1] + Write-Verbose -Verbose "previewPart: $previewPart" + + Write-Host "major: $major; minor: $minor; patch: $patch;" + + $vstsCommandString = "vso[task.setvariable variable=pwshMajorVersion]$major" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + + $vstsCommandString = "vso[task.setvariable variable=pwshMinorVersion]$minor" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + + $vstsCommandString = "vso[task.setvariable variable=pwshPatchVersion]$patch" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + if($previewPart) { + $vstsCommandString = "vso[task.setvariable variable=pwshPrereleaseVersion]$previewPart" + } else { + Write-Verbose -Verbose "No prerelease part found in version string." + } + displayName: Set ob_createvpack_*Ver + env: + ob_restore_phase: true + + # Validate pwsh*Version variables + - pwsh: | + $variables = @("pwshMajorVersion", "pwshMinorVersion", "pwshPatchVersion") + foreach ($var in $variables) { + if (-not (get-item "Env:\$var" -ErrorAction SilentlyContinue).value) { + throw "Required variable '`$env:$var' is not set." + } + } + displayName: Validate pwsh*Version variables + env: + ob_restore_phase: true + + - pwsh: | + if($env:RELEASETAGVAR -match '-') { + throw "Don't release a preview build without coordinating with Windows Engineering Build Tools Team" + } + displayName: Stop any preview release + env: + ob_restore_phase: true + + - task: UseDotNet@2 + displayName: 'Use .NET Core sdk' + inputs: + packageType: sdk + version: 3.1.x + installationPath: $(Agent.ToolsDirectory)/dotnet + + ### BUILD ### + + - template: /.pipelines/templates/insert-nuget-config-azfeed.yml@self + parameters: + repoRoot: $(repoRoot) + + - task: CodeQL3000Init@0 # Add CodeQL Init task right before your 'Build' step. + env: + ob_restore_phase: true # Set ob_restore_phase to run this step before '🔒 Setup Signing' step. + inputs: + Enabled: true + AnalyzeInPipeline: false # Do not upload results + Language: csharp + + - task: UseDotNet@2 + displayName: 'Install .NET based on global.json' + inputs: + useGlobalJson: true + workingDirectory: $(repoRoot) + env: + ob_restore_phase: true + + - pwsh: | + # Need to set PowerShellRoot variable for obp-file-signing template + $vstsCommandString = "vso[task.setvariable variable=PowerShellRoot]$(repoRoot)" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + + $Architecture = '$(Architecture)' + $runtime = switch ($Architecture) + { + "x64" { "win7-x64" } + "x86" { "win7-x86" } + "arm64" { "win-arm64" } + } + + $params = @{} + if ($env:BuildConfiguration -eq 'minSize') { + $params['ForMinimalSize'] = $true + } + + $vstsCommandString = "vso[task.setvariable variable=Runtime]$runtime" + Write-Host ("sending " + $vstsCommandString) + Write-Host "##$vstsCommandString" + + Write-Verbose -Message "Building PowerShell with Runtime: $runtime for '$env:BuildConfiguration' configuration" + Import-Module -Name $(repoRoot)/build.psm1 -Force + $buildWithSymbolsPath = New-Item -ItemType Directory -Path "$(Pipeline.Workspace)/Symbols_$Architecture" -Force + + Start-PSBootstrap -Scenario Package + $null = New-Item -ItemType Directory -Path $buildWithSymbolsPath -Force -Verbose + + $ReleaseTagParam = @{} + + if ($env:RELEASETAGVAR) { + $ReleaseTagParam['ReleaseTag'] = $env:RELEASETAGVAR + } + + Start-PSBuild -Runtime $runtime -Configuration Release -Output $buildWithSymbolsPath -Clean -PSModuleRestore @params @ReleaseTagParam + + $refFolderPath = Join-Path $buildWithSymbolsPath 'ref' + Write-Verbose -Verbose "refFolderPath: $refFolderPath" + $outputPath = Join-Path '$(ob_outputDirectory)' 'psoptions' + $null = New-Item -ItemType Directory -Path $outputPath -Force + $psOptPath = "$outputPath/psoptions.json" + Save-PSOptions -PSOptionsPath $psOptPath + + Write-Verbose -Verbose "Completed building PowerShell for '$env:BuildConfiguration' configuration" + displayName: Build Windows Universal - $(Architecture) -$(BuildConfiguration) Symbols folder + env: + __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) + ob_restore_phase: true # Set ob_restore_phase to run this step before '🔒 Setup Signing' step. + + - task: CodeQL3000Finalize@0 # Add CodeQL Finalize task right after your 'Build' step. + env: + ob_restore_phase: true # Set ob_restore_phase to run this step before '🔒 Setup Signing' step. + + - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 + displayName: 'Component Detection' + inputs: + sourceScanPath: '$(repoRoot)\src' + ob_restore_phase: true + + - template: /.pipelines/templates/obp-file-signing.yml@self + parameters: + binPath: '$(Pipeline.Workspace)/Symbols_$(Architecture)' + SigningProfile: $(windows_build_tools_cert_id) + OfficialBuild: false + vPackScenario: true + + ### END OF BUILD ### + + - pwsh: | + Get-ChildItem env:/ob_createvpack_*Ver + Get-ChildItem -Path "$(Pipeline.Workspace)\Symbols_$(Architecture)\*" -Recurse + Get-Content "$(Pipeline.Workspace)\PowerShell\preview.json" -ErrorAction SilentlyContinue | Write-Host + displayName: Debug Output Directory and Version + condition: succeededOrFailed() + + - pwsh: | + Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose + displayName: Capture Environment + condition: succeededOrFailed() + + - pwsh: | + $vpackFiles = Get-ChildItem -Path "$(Pipeline.Workspace)\Symbols_$(Architecture)\*" -Recurse + if($vpackFiles.Count -eq 0) { + throw "No files found in $(Pipeline.Workspace)\Symbols_$(Architecture)" + } + $vpackFiles + displayName: Debug Output Directory and Version + condition: succeededOrFailed() diff --git a/.pipelines/templates/testartifacts.yml b/.pipelines/templates/testartifacts.yml index 240ceae80f7..3a6bec4a859 100644 --- a/.pipelines/templates/testartifacts.yml +++ b/.pipelines/templates/testartifacts.yml @@ -1,8 +1,6 @@ jobs: - job: build_testartifacts_win variables: - - name: runCodesignValidationInjection - value: false - name: NugetSecurityAnalysisWarningLevel value: none - group: DotNetPrivateBuildAccess @@ -25,19 +23,16 @@ jobs: env: ob_restore_phase: true + - template: /.pipelines/templates/SetVersionVariables.yml@self + parameters: + ReleaseTagVar: $(ReleaseTagVar) + - template: /.pipelines/templates/insert-nuget-config-azfeed.yml@self parameters: - repoRoot: $(Build.SourcesDirectory)/PowerShell + repoRoot: $(RepoRoot) ob_restore_phase: true - - task: UseDotNet@2 - displayName: 'Use .NET Core sdk' - inputs: - useGlobalJson: true - packageType: 'sdk' - workingDirectory: $(Build.SourcesDirectory)/PowerShell" - env: - ob_restore_phase: true + - template: /.pipelines/templates/install-dotnet.yml@self - pwsh: | New-Item -Path '$(ob_outputDirectory)' -ItemType Directory -Force @@ -76,8 +71,6 @@ jobs: - job: build_testartifacts_nonwin variables: - - name: runCodesignValidationInjection - value: false - name: NugetSecurityAnalysisWarningLevel value: none - group: DotNetPrivateBuildAccess @@ -93,19 +86,16 @@ jobs: env: ob_restore_phase: true + - template: /.pipelines/templates/SetVersionVariables.yml@self + parameters: + ReleaseTagVar: $(ReleaseTagVar) + - template: /.pipelines/templates/insert-nuget-config-azfeed.yml@self parameters: repoRoot: $(Build.SourcesDirectory)/PowerShell ob_restore_phase: true - - task: UseDotNet@2 - displayName: 'Use .NET Core sdk' - inputs: - useGlobalJson: true - packageType: 'sdk' - workingDirectory: $(Build.SourcesDirectory)/PowerShell" - env: - ob_restore_phase: true + - template: /.pipelines/templates/install-dotnet.yml@self - pwsh: | New-Item -Path '$(ob_outputDirectory)' -ItemType Directory -Force diff --git a/.pipelines/templates/uploadToAzure.yml b/.pipelines/templates/uploadToAzure.yml index e698a7041da..de6f8e0d7fd 100644 --- a/.pipelines/templates/uploadToAzure.yml +++ b/.pipelines/templates/uploadToAzure.yml @@ -7,11 +7,9 @@ jobs: variables: - name: ob_sdl_sbom_enabled value: true - - name: runCodesignValidationInjection - value: false - name: NugetSecurityAnalysisWarningLevel value: none - - name: DOTNET_SKIP_FIRST_TIME_EXPERIENCE + - name: DOTNET_NOLOGO value: 1 - name: ob_outputDirectory value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' @@ -36,8 +34,7 @@ jobs: - template: /.pipelines/templates/SetVersionVariables.yml@self parameters: ReleaseTagVar: $(ReleaseTagVar) - CreateJson: yes - UseJson: no + CreateJson: no - template: /.pipelines/templates/release-SetReleaseTagandContainerName.yml@self @@ -152,10 +149,8 @@ jobs: buildType: 'current' artifact: drop_windows_package_package_win_arm64 itemPattern: | - **/*.msi **/*.msix **/*.zip - **/*.exe targetPath: '$(Build.ArtifactStagingDirectory)/downloads' displayName: Download windows arm64 packages @@ -188,10 +183,8 @@ jobs: buildType: 'current' artifact: drop_windows_package_package_win_x64 itemPattern: | - **/*.msi **/*.msix **/*.zip - **/*.exe targetPath: '$(Build.ArtifactStagingDirectory)/downloads' displayName: Download windows x64 packages @@ -200,10 +193,8 @@ jobs: buildType: 'current' artifact: drop_windows_package_package_win_x86 itemPattern: | - **/*.msi **/*.msix **/*.zip - **/*.exe targetPath: '$(Build.ArtifactStagingDirectory)/downloads' displayName: Download windows x86 packages @@ -249,17 +240,17 @@ jobs: - pwsh: | Write-Verbose -Verbose "Copying Github Release files in $(Build.ArtifactStagingDirectory)/downloads to use in Release Pipeline" - + Write-Verbose -Verbose "Creating output directory for GitHub Release files: $(ob_outputDirectory)/GitHubPackages" New-Item -Path $(ob_outputDirectory)/GitHubPackages -ItemType Directory -Force - Get-ChildItem -Path "$(Build.ArtifactStagingDirectory)/downloads/*" -Recurse | - Where-Object { $_.Extension -notin '.msix', '.nupkg' -and $_.Name -notmatch '-gc'} | + Get-ChildItem -Path "$(Build.ArtifactStagingDirectory)/downloads/*" -Recurse | + Where-Object { $_.Extension -notin '.msix', '.nupkg' -and $_.Name -notmatch '-gc'} | Copy-Item -Destination $(ob_outputDirectory)/GitHubPackages -Recurse -Verbose - + Write-Verbose -Verbose "Creating output directory for NuGet packages: $(ob_outputDirectory)/NuGetPackages" New-Item -Path $(ob_outputDirectory)/NuGetPackages -ItemType Directory -Force - Get-ChildItem -Path "$(Build.ArtifactStagingDirectory)/downloads/*" -Recurse | - Where-Object { $_.Extension -eq '.nupkg' } | + Get-ChildItem -Path "$(Build.ArtifactStagingDirectory)/downloads/*" -Recurse | + Where-Object { $_.Extension -eq '.nupkg' } | Copy-Item -Destination $(ob_outputDirectory)/NuGetPackages -Recurse -Verbose displayName: Copy downloads to Artifacts @@ -398,7 +389,7 @@ jobs: } # To use -Include parameter, we need to use \* to get all files - $files = Get-ChildItem -Path $downloadsDirectory\* -Include @("*.deb", "*.tar.gz", "*.rpm", "*.msi", "*.zip", "*.pkg") + $files = Get-ChildItem -Path $downloadsDirectory\* -Include @("*.deb", "*.tar.gz", "*.rpm", "*.zip", "*.pkg") Write-Verbose -Verbose "files to upload." $files | Write-Verbose -Verbose diff --git a/.pipelines/templates/variables/PowerShell-Coordinated_Packages-Variables.yml b/.pipelines/templates/variables/PowerShell-Coordinated_Packages-Variables.yml new file mode 100644 index 00000000000..dd67d509a8a --- /dev/null +++ b/.pipelines/templates/variables/PowerShell-Coordinated_Packages-Variables.yml @@ -0,0 +1,67 @@ +parameters: + - name: InternalSDKBlobURL + type: string + default: ' ' + - name: ReleaseTagVar + type: string + default: 'fromBranch' + - name: SKIP_SIGNING + type: string + default: 'NO' + - name: ENABLE_MSBUILD_BINLOGS + type: boolean + default: false + - name: FORCE_CODEQL + type: boolean + default: false + +variables: + - name: PS_RELEASE_BUILD + value: 1 + - name: DOTNET_CLI_TELEMETRY_OPTOUT + value: 1 + - name: POWERSHELL_TELEMETRY_OPTOUT + value: 1 + - name: nugetMultiFeedWarnLevel + value: none + - name: NugetSecurityAnalysisWarningLevel + value: none + - name: skipNugetSecurityAnalysis + value: true + - name: branchCounterKey + value: $[format('{0:yyyyMMdd}-{1}', pipeline.startTime,variables['Build.SourceBranch'])] + - name: branchCounter + value: $[counter(variables['branchCounterKey'], 1)] + - name: BUILDSECMON_OPT_IN + value: true + - name: __DOTNET_RUNTIME_FEED + value: ${{ parameters.InternalSDKBlobURL }} + - name: LinuxContainerImage + value: mcr.microsoft.com/onebranch/azurelinux/build:3.0 + - name: WindowsContainerImage + value: onebranch.azurecr.io/windows/ltsc2022/vse2022:latest + - name: CDP_DEFINITION_BUILD_COUNT + value: $[counter('', 0)] + - name: ReleaseTagVar + value: ${{ parameters.ReleaseTagVar }} + - name: SKIP_SIGNING + value: ${{ parameters.SKIP_SIGNING }} + - group: mscodehub-feed-read-general + - group: mscodehub-feed-read-akv + - name: ENABLE_MSBUILD_BINLOGS + value: ${{ parameters.ENABLE_MSBUILD_BINLOGS }} + - ${{ if eq(parameters['FORCE_CODEQL'],'true') }}: + # Cadence is hours before CodeQL will allow a re-upload of the database + - name: CodeQL.Cadence + value: 1 + - name: CODEQL_ENABLED + ${{ if or(eq(variables['Build.SourceBranch'], 'refs/heads/master'), eq(parameters['FORCE_CODEQL'],'true')) }}: + value: true + ${{ else }}: + value: false + # Fix for BinSkim ICU package error in Linux containers + - name: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT + value: true + # Disable BinSkim at job level to override NonOfficial template defaults + - name: ob_sdl_binskim_enabled + value: false diff --git a/.pipelines/templates/variables/PowerShell-Packages-Variables.yml b/.pipelines/templates/variables/PowerShell-Packages-Variables.yml new file mode 100644 index 00000000000..7d1818909b5 --- /dev/null +++ b/.pipelines/templates/variables/PowerShell-Packages-Variables.yml @@ -0,0 +1,50 @@ +parameters: + - name: debug + type: boolean + default: false + - name: ForceAzureBlobDelete + type: string + default: 'false' + - name: ReleaseTagVar + type: string + default: 'fromBranch' + - name: disableNetworkIsolation + type: boolean + default: false + +variables: + - name: CDP_DEFINITION_BUILD_COUNT + value: $[counter('', 0)] # needed for onebranch.pipeline.version task + - name: system.debug + value: ${{ parameters.debug }} + - name: ENABLE_PRS_DELAYSIGN + value: 1 + - name: ROOT + value: $(Build.SourcesDirectory) + - name: ForceAzureBlobDelete + value: ${{ parameters.ForceAzureBlobDelete }} + - name: NUGET_XMLDOC_MODE + value: none + - name: nugetMultiFeedWarnLevel + value: none + - name: NugetSecurityAnalysisWarningLevel + value: none + - name: skipNugetSecurityAnalysis + value: true + - name: ReleaseTagVar + value: ${{ parameters.ReleaseTagVar }} + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' + - name: WindowsContainerImage + value: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' # Docker image which is used to build the project + - name: LinuxContainerImage + value: mcr.microsoft.com/onebranch/azurelinux/build:3.0 + - group: mscodehub-feed-read-general + - group: mscodehub-feed-read-akv + - name: branchCounterKey + value: $[format('{0:yyyyMMdd}-{1}', pipeline.startTime,variables['Build.SourceBranch'])] + - name: branchCounter + value: $[counter(variables['branchCounterKey'], 1)] + - group: MSIXSigningProfile + - name: disableNetworkIsolation + value: ${{ parameters.disableNetworkIsolation }} diff --git a/.pipelines/templates/variables/PowerShell-Release-Azure-Variables.yml b/.pipelines/templates/variables/PowerShell-Release-Azure-Variables.yml new file mode 100644 index 00000000000..3b47e5eff2b --- /dev/null +++ b/.pipelines/templates/variables/PowerShell-Release-Azure-Variables.yml @@ -0,0 +1,35 @@ +parameters: + - name: debug + type: boolean + default: false + +variables: + - name: CDP_DEFINITION_BUILD_COUNT + value: $[counter('', 0)] + - name: system.debug + value: ${{ parameters.debug }} + - name: ENABLE_PRS_DELAYSIGN + value: 1 + - name: ROOT + value: $(Build.SourcesDirectory) + - name: REPOROOT + value: $(Build.SourcesDirectory) + - name: OUTPUTROOT + value: $(REPOROOT)\out + - name: NUGET_XMLDOC_MODE + value: none + - name: nugetMultiFeedWarnLevel + value: none + - name: NugetSecurityAnalysisWarningLevel + value: none + - name: skipNugetSecurityAnalysis + value: true + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' + - name: ob_sdl_tsa_configFile + value: $(Build.SourcesDirectory)\.config\tsaoptions.json + - name: WindowsContainerImage + value: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' + - name: LinuxContainerImage + value: mcr.microsoft.com/onebranch/azurelinux/build:3.0 + - group: PoolNames diff --git a/.pipelines/templates/variables/PowerShell-Release-Variables.yml b/.pipelines/templates/variables/PowerShell-Release-Variables.yml new file mode 100644 index 00000000000..930c559eafe --- /dev/null +++ b/.pipelines/templates/variables/PowerShell-Release-Variables.yml @@ -0,0 +1,41 @@ +parameters: + - name: debug + type: boolean + default: false + - name: ReleaseTagVar + type: string + default: 'fromBranch' + +variables: + - name: CDP_DEFINITION_BUILD_COUNT + value: $[counter('', 0)] + - name: system.debug + value: ${{ parameters.debug }} + - name: ENABLE_PRS_DELAYSIGN + value: 1 + - name: ROOT + value: $(Build.SourcesDirectory) + - name: REPOROOT + value: $(Build.SourcesDirectory) + - name: OUTPUTROOT + value: $(REPOROOT)\out + - name: NUGET_XMLDOC_MODE + value: none + - name: nugetMultiFeedWarnLevel + value: none + - name: NugetSecurityAnalysisWarningLevel + value: none + - name: skipNugetSecurityAnalysis + value: true + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' + - name: WindowsContainerImage + value: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' + - name: LinuxContainerImage + value: mcr.microsoft.com/onebranch/azurelinux/build:3.0 + - name: ReleaseTagVar + value: ${{ parameters.ReleaseTagVar }} + - group: PoolNames + # Fix for BinSkim ICU package error in Linux containers + - name: DOTNET_SYSTEM_GLOBALIZATION_INVARIANT + value: true diff --git a/.pipelines/templates/variables/PowerShell-vPack-Variables.yml b/.pipelines/templates/variables/PowerShell-vPack-Variables.yml new file mode 100644 index 00000000000..7f00a5e0e2a --- /dev/null +++ b/.pipelines/templates/variables/PowerShell-vPack-Variables.yml @@ -0,0 +1,39 @@ +parameters: + - name: debug + type: boolean + default: false + - name: ReleaseTagVar + type: string + default: 'fromBranch' + - name: netiso + type: string + default: 'R1' + +variables: + - name: CDP_DEFINITION_BUILD_COUNT + value: $[counter('', 0)] + - name: system.debug + value: ${{ parameters.debug }} + - name: BuildSolution + value: $(Build.SourcesDirectory)\dirs.proj + - name: BuildConfiguration + value: Release + - name: WindowsContainerImage + value: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' + - name: Codeql.Enabled + value: false # pipeline is not building artifacts; it repackages existing artifacts into a vpack + - name: DOTNET_CLI_TELEMETRY_OPTOUT + value: 1 + - name: POWERSHELL_TELEMETRY_OPTOUT + value: 1 + - name: nugetMultiFeedWarnLevel + value: none + - name: ReleaseTagVar + value: ${{ parameters.ReleaseTagVar }} + - group: Azure Blob variable group + - group: certificate_logical_to_actual # used within signing task + - group: DotNetPrivateBuildAccess + - name: netiso + value: ${{ parameters.netiso }} +# We shouldn't be using PATs anymore +# - group: mscodehub-feed-read-general diff --git a/.pipelines/templates/variable/release-shared.yml b/.pipelines/templates/variables/release-shared.yml similarity index 91% rename from .pipelines/templates/variable/release-shared.yml rename to .pipelines/templates/variables/release-shared.yml index f944639a908..70d3dd2df97 100644 --- a/.pipelines/templates/variable/release-shared.yml +++ b/.pipelines/templates/variables/release-shared.yml @@ -17,9 +17,7 @@ variables: value: false - name: ob_sdl_sbom_enabled value: ${{ parameters.SBOM }} - - name: runCodesignValidationInjection - value: false - - name: DOTNET_SKIP_FIRST_TIME_EXPERIENCE + - name: DOTNET_NOLOGO value: 1 - group: 'mscodehub-code-read-akv' - group: 'Azure Blob variable group' diff --git a/.pipelines/templates/windows-hosted-build.yml b/.pipelines/templates/windows-hosted-build.yml index 929aa54b8a7..0f7c1f8fb2e 100644 --- a/.pipelines/templates/windows-hosted-build.yml +++ b/.pipelines/templates/windows-hosted-build.yml @@ -10,11 +10,9 @@ jobs: pool: type: windows variables: - - name: runCodesignValidationInjection - value: false - name: NugetSecurityAnalysisWarningLevel value: none - - name: DOTNET_SKIP_FIRST_TIME_EXPERIENCE + - name: DOTNET_NOLOGO value: 1 - group: DotNetPrivateBuildAccess - group: certificate_logical_to_actual @@ -65,12 +63,7 @@ jobs: AnalyzeInPipeline: false Language: csharp - - task: UseDotNet@2 - inputs: - useGlobalJson: true - workingDirectory: $(PowerShellRoot) - env: - ob_restore_phase: true + - template: /.pipelines/templates/install-dotnet.yml@self - pwsh: | $runtime = switch ($env:Architecture) @@ -144,6 +137,7 @@ jobs: } Import-Module -Name $(PowerShellRoot)/build.psm1 -Force + Find-Dotnet ## Build global tool Write-Verbose -Message "Building PowerShell global tool for Windows.x64" -Verbose @@ -206,6 +200,7 @@ jobs: - template: /.pipelines/templates/obp-file-signing.yml@self parameters: binPath: '$(Pipeline.Workspace)/Symbols_$(Architecture)' + OfficialBuild: $(ps_official_build) ## first we sign all the files in the bin folder - ${{ if eq(variables['Architecture'], 'fxdependent') }}: @@ -213,6 +208,7 @@ jobs: parameters: binPath: '$(GlobalToolArtifactPath)/publish/PowerShell.Windows.x64/release' globalTool: 'true' + OfficialBuild: $(ps_official_build) - pwsh: | Get-ChildItem '$(GlobalToolArtifactPath)/obj/PowerShell.Windows.x64/release' @@ -237,6 +233,9 @@ jobs: After that, we repack using Compress-Archive and rename it back to a nupkg. #> + Import-Module -Name $(PowerShellRoot)/build.psm1 -Force + Find-Dotnet + $packagingStrings = Import-PowerShellDataFile "$(PowerShellRoot)\tools\packaging\packaging.strings.psd1" $outputPath = Join-Path '$(ob_outputDirectory)' 'globaltool' @@ -271,11 +270,11 @@ jobs: 'Microsoft.PowerShell.PSResourceGet' 'Microsoft.PowerShell.Archive' 'PSReadLine' - 'ThreadJob' + 'Microsoft.PowerShell.ThreadJob' ) $sourceModulePath = Join-Path '$(GlobalToolArtifactPath)' 'publish' 'PowerShell.Windows.x64' 'release' 'Modules' - $destModulesPath = Join-Path "$outputPath" 'temp' 'tools' 'net10.0' 'any' 'modules' + $destModulesPath = Join-Path "$outputPath" 'temp' 'tools' 'net11.0' 'any' 'modules' $modulesToCopy | ForEach-Object { $modulePath = Join-Path $sourceModulePath $_ @@ -283,7 +282,7 @@ jobs: } # Copy ref assemblies - Copy-Item '$(Pipeline.Workspace)/Symbols_$(Architecture)/ref' "$outputPath\temp\tools\net10.0\any\ref" -Recurse -Force + Copy-Item '$(Pipeline.Workspace)/Symbols_$(Architecture)/ref' "$outputPath\temp\tools\net11.0\any\ref" -Recurse -Force $contentPath = Join-Path "$outputPath\temp" 'content' $contentFilesPath = Join-Path "$outputPath\temp" 'contentFiles' @@ -291,14 +290,14 @@ jobs: Remove-Item -Path $contentPath,$contentFilesPath -Recurse -Force # remove PDBs to reduce the size of the nupkg - Remove-Item -Path "$outputPath\temp\tools\net10.0\any\*.pdb" -Recurse -Force + Remove-Item -Path "$outputPath\temp\tools\net11.0\any\*.pdb" -Recurse -Force # create powershell.config.json $config = [ordered]@{} $config.Add("Microsoft.PowerShell:ExecutionPolicy", "RemoteSigned") $config.Add("WindowsPowerShellCompatibilityModuleDenyList", @("PSScheduledJob", "BestPractices", "UpdateServices")) - $configPublishPath = Join-Path "$outputPath" 'temp' 'tools' 'net10.0' 'any' "powershell.config.json" + $configPublishPath = Join-Path "$outputPath" 'temp' 'tools' 'net11.0' 'any' "powershell.config.json" Set-Content -Path $configPublishPath -Value ($config | ConvertTo-Json) -Force -ErrorAction Stop Compress-Archive -Path "$outputPath\temp\*" -DestinationPath "$outputPath\$nupkgName" -Force @@ -316,7 +315,7 @@ jobs: displayName: Sign nupkg files inputs: command: 'sign' - cp_code: 'CP-401405' + cp_code: '$(nuget_cert_id)' files_to_sign: '**\*.nupkg' search_root: '$(ob_outputDirectory)\globaltool' condition: and(succeeded(), eq(variables['Architecture'], 'fxdependent')) diff --git a/.spelling b/.spelling index dbc54bbc393..cc711f0aa5a 100644 --- a/.spelling +++ b/.spelling @@ -622,6 +622,7 @@ NativeCommandProcessor.cs NativeCultureResolver nativeexecution net5.0 +net10.0 netcoreapp5.0 netip.ps1. netstandard.dll diff --git a/.vsts-ci/linux-daily.yml b/.vsts-ci/linux-daily.yml index c1dd96fd0b4..10effadd1e3 100644 --- a/.vsts-ci/linux-daily.yml +++ b/.vsts-ci/linux-daily.yml @@ -25,8 +25,7 @@ pr: variables: DOTNET_CLI_TELEMETRY_OPTOUT: 1 POWERSHELL_TELEMETRY_OPTOUT: 1 - # Avoid expensive initialization of dotnet cli, see: https://donovanbrown.com/post/Stop-wasting-time-during-NET-Core-builds - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 + DOTNET_NOLOGO: 1 __SuppressAnsiEscapeSequences: 1 resources: diff --git a/.vsts-ci/linux-internal.yml b/.vsts-ci/linux-internal.yml index 6286a03fb52..b90ab0d9eb4 100644 --- a/.vsts-ci/linux-internal.yml +++ b/.vsts-ci/linux-internal.yml @@ -34,7 +34,7 @@ pr: - .vsts-ci/misc-analysis.yml - .vsts-ci/windows.yml - .vsts-ci/windows/* - - tools/cgmanifest.json + - tools/cgmanifest/* - LICENSE.txt - test/common/markdown/* - test/perf/* @@ -48,8 +48,7 @@ pr: variables: DOTNET_CLI_TELEMETRY_OPTOUT: 1 POWERSHELL_TELEMETRY_OPTOUT: 1 - # Avoid expensive initialization of dotnet cli, see: https://donovanbrown.com/post/Stop-wasting-time-during-NET-Core-builds - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 + DOTNET_NOLOGO: 1 __SuppressAnsiEscapeSequences: 1 nugetMultiFeedWarnLevel: none diff --git a/.vsts-ci/linux.yml b/.vsts-ci/linux.yml index b386b9c7eb3..5d9dc663e1c 100644 --- a/.vsts-ci/linux.yml +++ b/.vsts-ci/linux.yml @@ -48,8 +48,7 @@ pr: variables: DOTNET_CLI_TELEMETRY_OPTOUT: 1 POWERSHELL_TELEMETRY_OPTOUT: 1 - # Avoid expensive initialization of dotnet cli, see: https://donovanbrown.com/post/Stop-wasting-time-during-NET-Core-builds - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 + DOTNET_NOLOGO: 1 __SuppressAnsiEscapeSequences: 1 nugetMultiFeedWarnLevel: none diff --git a/.vsts-ci/mac.yml b/.vsts-ci/mac.yml index 05d6d71ea71..678ded65259 100644 --- a/.vsts-ci/mac.yml +++ b/.vsts-ci/mac.yml @@ -34,7 +34,7 @@ pr: - .vsts-ci/misc-analysis.yml - .vsts-ci/windows.yml - .vsts-ci/windows/* - - tools/cgmanifest.json + - tools/cgmanifest/* - LICENSE.txt - test/common/markdown/* - test/perf/* @@ -48,8 +48,7 @@ pr: variables: DOTNET_CLI_TELEMETRY_OPTOUT: 1 POWERSHELL_TELEMETRY_OPTOUT: 1 - # Avoid expensive initialization of dotnet cli, see: https://donovanbrown.com/post/Stop-wasting-time-during-NET-Core-builds - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 + DOTNET_NOLOGO: 1 # Turn off Homebrew analytics HOMEBREW_NO_ANALYTICS: 1 __SuppressAnsiEscapeSequences: 1 diff --git a/.vsts-ci/psresourceget-acr.yml b/.vsts-ci/psresourceget-acr.yml index 1a24983b5b5..225e2699533 100644 --- a/.vsts-ci/psresourceget-acr.yml +++ b/.vsts-ci/psresourceget-acr.yml @@ -34,7 +34,7 @@ pr: - .github/ISSUE_TEMPLATE/* - .github/workflows/* - .vsts-ci/misc-analysis.yml - - tools/cgmanifest.json + - tools/cgmanifest/* - LICENSE.txt - test/common/markdown/* - test/perf/* @@ -49,8 +49,7 @@ variables: GIT_CONFIG_PARAMETERS: "'core.autocrlf=false'" DOTNET_CLI_TELEMETRY_OPTOUT: 1 POWERSHELL_TELEMETRY_OPTOUT: 1 - # Avoid expensive initialization of dotnet cli, see: https://donovanbrown.com/post/Stop-wasting-time-during-NET-Core-builds - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 + DOTNET_NOLOGO: 1 __SuppressAnsiEscapeSequences: 1 NugetSecurityAnalysisWarningLevel: none nugetMultiFeedWarnLevel: none @@ -154,4 +153,3 @@ stages: Publish-TestResults -Title "PSResourceGet - ACR tests" -Path $outputFilePath -Type NUnit displayName: 'PSResourceGet ACR functional tests using AzAuth' - diff --git a/.vsts-ci/sshremoting-tests.yml b/.vsts-ci/sshremoting-tests.yml index 2eda2a18276..72c5710016b 100644 --- a/.vsts-ci/sshremoting-tests.yml +++ b/.vsts-ci/sshremoting-tests.yml @@ -27,8 +27,7 @@ variables: value: 1 - name: POWERSHELL_TELEMETRY_OPTOUT value: 1 - # Avoid expensive initialization of dotnet cli, see: https://donovanbrown.com/post/Stop-wasting-time-during-NET-Core-builds - - name: DOTNET_SKIP_FIRST_TIME_EXPERIENCE + - name: DOTNET_NOLOGO value: 1 - name: __SuppressAnsiEscapeSequences value: 1 diff --git a/.vsts-ci/templates/nanoserver.yml b/.vsts-ci/templates/nanoserver.yml deleted file mode 100644 index ae9f639b3b2..00000000000 --- a/.vsts-ci/templates/nanoserver.yml +++ /dev/null @@ -1,61 +0,0 @@ -parameters: - vmImage: 'windows-latest' - jobName: 'Nanoserver_Tests' - continueOnError: false - -jobs: - -- job: ${{ parameters.jobName }} - variables: - scriptName: ${{ parameters.scriptName }} - - pool: - vmImage: ${{ parameters.vmImage }} - - displayName: ${{ parameters.jobName }} - - steps: - - script: | - set - displayName: Capture Environment - condition: succeededOrFailed() - - - task: DownloadBuildArtifacts@0 - displayName: 'Download Build Artifacts' - inputs: - downloadType: specific - itemPattern: | - build/**/* - downloadPath: '$(System.ArtifactsDirectory)' - - - pwsh: | - Get-ChildItem "$(System.ArtifactsDirectory)\*" -Recurse - displayName: 'Capture Artifacts Directory' - continueOnError: true - - - pwsh: | - Install-module Pester -Scope CurrentUser -Force -MaximumVersion 4.99 - displayName: 'Install Pester' - continueOnError: true - - - pwsh: | - Import-Module .\tools\ci.psm1 - Restore-PSOptions -PSOptionsPath '$(System.ArtifactsDirectory)\build\psoptions.json' - $options = (Get-PSOptions) - $path = split-path -path $options.Output - Write-Verbose "Path: '$path'" -Verbose - $rootPath = split-Path -path $path - Expand-Archive -Path '$(System.ArtifactsDirectory)\build\build.zip' -DestinationPath $rootPath -Force - Invoke-Pester -Path ./test/nanoserver -OutputFormat NUnitXml -OutputFile ./test-nanoserver.xml - displayName: Test - condition: succeeded() - - - task: PublishTestResults@2 - condition: succeededOrFailed() - displayName: Publish Nanoserver Test Results **\test*.xml - inputs: - testRunner: NUnit - testResultsFiles: '**\test*.xml' - testRunTitle: nanoserver - mergeTestResults: true - failTaskOnFailedTests: true diff --git a/.vsts-ci/templates/windows-test.yml b/.vsts-ci/templates/windows-test.yml deleted file mode 100644 index 22758e3953c..00000000000 --- a/.vsts-ci/templates/windows-test.yml +++ /dev/null @@ -1,92 +0,0 @@ -parameters: - pool: 'windows-2019' - imageName: 'PSWindows11-ARM64' - parentJobs: [] - purpose: '' - tagSet: 'CI' - -jobs: -- job: win_test_${{ parameters.purpose }}_${{ parameters.tagSet }} - dependsOn: - ${{ parameters.parentJobs }} - pool: - ${{ if startsWith( parameters.pool, 'windows-') }}: - vmImage: ${{ parameters.pool }} - ${{ else }}: - name: ${{ parameters.pool }} - demands: - - ImageOverride -equals ${{ parameters.imageName }} - - displayName: Windows Test - ${{ parameters.purpose }} - ${{ parameters.tagSet }} - - steps: - - powershell: | - [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 - $pwsh = Get-Command pwsh -ErrorAction SilentlyContinue -CommandType Application - - if ($null -eq $pwsh) { - $powerShellPath = Join-Path -Path $env:AGENT_TEMPDIRECTORY -ChildPath 'powershell' - Invoke-WebRequest -Uri https://raw.githubusercontent.com/PowerShell/PowerShell/master/tools/install-powershell.ps1 -outfile ./install-powershell.ps1 - ./install-powershell.ps1 -Destination $powerShellPath - $vstsCommandString = "vso[task.setvariable variable=PATH]$powerShellPath;$env:PATH" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - } - - displayName: Install PowerShell if missing - condition: ne('${{ parameters.pool }}', 'windows-2019') - - - pwsh: | - Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose - displayName: Capture Environment - condition: succeededOrFailed() - - - task: DownloadBuildArtifacts@0 - displayName: 'Download Build Artifacts' - inputs: - downloadType: specific - itemPattern: | - build/**/* - downloadPath: '$(System.ArtifactsDirectory)' - - - pwsh: | - Get-ChildItem "$(System.ArtifactsDirectory)\*" -Recurse - displayName: 'Capture Artifacts Directory' - continueOnError: true - - - task: UseDotNet@2 - displayName: 'Use .NET Core sdk' - inputs: - useGlobalJson: true - packageType: 'sdk' - workingDirectory: $(Build.SourcesDirectory)" - - # must be run frow Windows PowerShell - - powershell: | - # Remove "Program Files\dotnet" from the env variable PATH, so old SDKs won't affect us. - Write-Host "Old Path:" - Write-Host $env:Path - - $dotnetPath = Join-Path $env:SystemDrive 'Program Files\dotnet' - $paths = $env:Path -split ";" | Where-Object { -not $_.StartsWith($dotnetPath) } - $env:Path = $paths -join ";" - - Write-Host "New Path:" - Write-Host $env:Path - - # Bootstrap - Import-Module .\tools\ci.psm1 - Invoke-CIInstall - displayName: Bootstrap - - - pwsh: | - Import-Module .\build.psm1 -force - Import-Module .\tools\ci.psm1 - Restore-PSOptions -PSOptionsPath '$(System.ArtifactsDirectory)\build\psoptions.json' - $options = (Get-PSOptions) - $path = split-path -path $options.Output - $rootPath = split-Path -path $path - Expand-Archive -Path '$(System.ArtifactsDirectory)\build\build.zip' -DestinationPath $rootPath -Force - Invoke-CITest -Purpose '${{ parameters.purpose }}' -TagSet '${{ parameters.tagSet }}' - displayName: Test - condition: succeeded() diff --git a/.vsts-ci/windows-arm64.yml b/.vsts-ci/windows-arm64.yml index be4cfcbaf4c..1c4bc2ee8af 100644 --- a/.vsts-ci/windows-arm64.yml +++ b/.vsts-ci/windows-arm64.yml @@ -28,7 +28,7 @@ pr: - .dependabot/config.yml - .github/ISSUE_TEMPLATE/* - .vsts-ci/misc-analysis.yml - - tools/cgmanifest.json + - tools/cgmanifest/* - LICENSE.txt - test/common/markdown/* - test/perf/* @@ -45,8 +45,7 @@ variables: value: 1 - name: POWERSHELL_TELEMETRY_OPTOUT value: 1 - # Avoid expensive initialization of dotnet cli, see: https://donovanbrown.com/post/Stop-wasting-time-during-NET-Core-builds - - name: DOTNET_SKIP_FIRST_TIME_EXPERIENCE + - name: DOTNET_NOLOGO value: 1 - name: __SuppressAnsiEscapeSequences value: 1 diff --git a/.vsts-ci/windows-daily.yml b/.vsts-ci/windows-daily.yml deleted file mode 100644 index 59dd3ba2f36..00000000000 --- a/.vsts-ci/windows-daily.yml +++ /dev/null @@ -1,149 +0,0 @@ -name: PR-$(System.PullRequest.PullRequestNumber)-$(Date:yyyyMMdd)$(Rev:.rr) -trigger: - # Batch merge builds together while a merge build is running - batch: true - branches: - include: - - master - - release* - - feature* - paths: - include: - - '*' - exclude: - - /.vsts-ci/misc-analysis.yml - - /.github/ISSUE_TEMPLATE/* - - /.dependabot/config.yml -pr: - branches: - include: - - master - - release* - - feature* - paths: - include: - - '*' - exclude: - - /.vsts-ci/misc-analysis.yml - - /.github/ISSUE_TEMPLATE/* - - /.dependabot/config.yml - -variables: - GIT_CONFIG_PARAMETERS: "'core.autocrlf=false'" - DOTNET_CLI_TELEMETRY_OPTOUT: 1 - POWERSHELL_TELEMETRY_OPTOUT: 1 - # Avoid expensive initialization of dotnet cli, see: https://donovanbrown.com/post/Stop-wasting-time-during-NET-Core-builds - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 - __SuppressAnsiEscapeSequences: 1 - -resources: -- repo: self - clean: true - -stages: -- stage: BuildWin - displayName: Build for Windows - jobs: - - template: templates/ci-build.yml - -- stage: TestWin - displayName: Test for Windows - jobs: - - job: win_test - pool: - vmImage: windows-2019 - displayName: Windows Test - timeoutInMinutes: 90 - - steps: - - pwsh: | - Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose - displayName: 'Capture Environment' - condition: succeededOrFailed() - - - task: DownloadBuildArtifacts@0 - displayName: 'Download Build Artifacts' - inputs: - downloadType: specific - itemPattern: | - build/**/* - xunit/**/* - downloadPath: '$(System.ArtifactsDirectory)' - - - pwsh: | - Get-ChildItem "$(System.ArtifactsDirectory)\*" -Recurse - displayName: 'Capture Artifacts Directory' - continueOnError: true - - # must be run frow Windows PowerShell - - powershell: | - # Remove "Program Files\dotnet" from the env variable PATH, so old SDKs won't affect us. - Write-Host "Old Path:" - Write-Host $env:Path - - $dotnetPath = Join-Path $env:SystemDrive 'Program Files\dotnet' - $paths = $env:Path -split ";" | Where-Object { -not $_.StartsWith($dotnetPath) } - $env:Path = $paths -join ";" - - Write-Host "New Path:" - Write-Host $env:Path - - Import-Module .\tools\ci.psm1 - Invoke-CIInstall - displayName: Bootstrap - condition: succeededOrFailed() - - - task: UseDotNet@2 - displayName: 'Use .NET Core sdk' - inputs: - useGlobalJson: true - packageType: 'sdk' - workingDirectory: $(Build.SourcesDirectory)" - - - pwsh: | - Import-Module .\build.psm1 - Restore-PSOptions -PSOptionsPath '$(System.ArtifactsDirectory)\build\psoptions.json' - $path = Split-Path -Parent (Get-PSOutput -Options (Get-PSOptions)) - $rootPath = Split-Path -Path $path - Expand-Archive -Path '$(System.ArtifactsDirectory)\build\build.zip' -DestinationPath $rootPath -Force - displayName: 'Unzip Build' - condition: succeeded() - - - pwsh: | - Import-Module .\build.psm1 - Import-Module .\tools\ci.psm1 - Restore-PSOptions -PSOptionsPath '$(System.ArtifactsDirectory)\build\psoptions.json' - Invoke-CITest -Purpose UnelevatedPesterTests -TagSet CI - displayName: Test - UnelevatedPesterTests - CI - condition: succeeded() - - - pwsh: | - Import-Module .\build.psm1 - Import-Module .\tools\ci.psm1 - Restore-PSOptions -PSOptionsPath '$(System.ArtifactsDirectory)\build\psoptions.json' - Invoke-CITest -Purpose ElevatedPesterTests -TagSet CI - displayName: Test - ElevatedPesterTests - CI - condition: succeededOrFailed() - - - pwsh: | - Import-Module .\build.psm1 - Import-Module .\tools\ci.psm1 - Restore-PSOptions -PSOptionsPath '$(System.ArtifactsDirectory)\build\psoptions.json' - Invoke-CITest -Purpose UnelevatedPesterTests -TagSet Others - displayName: Test - UnelevatedPesterTests - Others - condition: succeededOrFailed() - - - pwsh: | - Import-Module .\build.psm1 - Import-Module .\tools\ci.psm1 - Restore-PSOptions -PSOptionsPath '$(System.ArtifactsDirectory)\build\psoptions.json' - Invoke-CITest -Purpose ElevatedPesterTests -TagSet Others - displayName: Test - ElevatedPesterTests - Others - condition: succeededOrFailed() - - - pwsh: | - Import-Module .\build.psm1 - $xUnitTestResultsFile = '$(System.ArtifactsDirectory)\xunit\xUnitTestResults.xml' - Test-XUnitTestResults -TestResultsFile $xUnitTestResultsFile - displayName: Verify xUnit Test Results - condition: succeededOrFailed() diff --git a/.vsts-ci/windows.yml b/.vsts-ci/windows.yml index c0f08f54a41..4171d09643d 100644 --- a/.vsts-ci/windows.yml +++ b/.vsts-ci/windows.yml @@ -42,8 +42,7 @@ variables: GIT_CONFIG_PARAMETERS: "'core.autocrlf=false'" DOTNET_CLI_TELEMETRY_OPTOUT: 1 POWERSHELL_TELEMETRY_OPTOUT: 1 - # Avoid expensive initialization of dotnet cli, see: https://donovanbrown.com/post/Stop-wasting-time-during-NET-Core-builds - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 + DOTNET_NOLOGO: 1 __SuppressAnsiEscapeSequences: 1 NugetSecurityAnalysisWarningLevel: none nugetMultiFeedWarnLevel: none diff --git a/.vsts-ci/windows/windows-packaging.yml b/.vsts-ci/windows/windows-packaging.yml index 05f69400719..6b73ca05723 100644 --- a/.vsts-ci/windows/windows-packaging.yml +++ b/.vsts-ci/windows/windows-packaging.yml @@ -47,8 +47,7 @@ variables: value: 1 - name: POWERSHELL_TELEMETRY_OPTOUT value: 1 - # Avoid expensive initialization of dotnet cli, see: https://donovanbrown.com/post/Stop-wasting-time-during-NET-Core-builds - - name: DOTNET_SKIP_FIRST_TIME_EXPERIENCE + - name: DOTNET_NOLOGO value: 1 - name: __SuppressAnsiEscapeSequences value: 1 diff --git a/CHANGELOG/7.4.md b/CHANGELOG/7.4.md index a04b7c82e6e..eb506a7ddbc 100644 --- a/CHANGELOG/7.4.md +++ b/CHANGELOG/7.4.md @@ -1,5 +1,197 @@ # 7.4 Changelog +## [7.4.15] + +### General Cmdlet Updates and Fixes + +- Delay update notification for one week to ensure all packages become available (#27229) +- Close pipe client handles after creating the child ssh process (#27139) + +### Tests + +- Fix the `PSNativeCommandArgumentPassing` test (#27146) + +### Build and Packaging Improvements + +
+ + + +

Update .NET SDK to 8.0.420

+ +
+ +
    +
  • Fix the container image for vPack, MSIX vPack and Package pipelines (#27018)
  • +
  • Update branch for release (#27279)
  • +
  • Fix package pipeline by adding in PDP-Media directory (#27255)
  • +
  • Pin ready-to-merge.yml reusable workflow to commit SHA (#27247)
  • +
  • [StepSecurity] ci: Harden GitHub Actions tags (#27244)
  • +
  • Build, package, and create VPack for the PowerShell-LTS store package within the same msixbundle-vpack pipeline (#27242)
  • +
  • Change the display name of PowerShell-LTS package to PowerShell LTS (#27232)
  • +
  • [StepSecurity] ci: Harden GitHub Actions tokens (#27231)
  • +
  • Redo windows image fix to use latest image (#27230)
  • +
  • Separate Store Package Creation, Skip Polling for Store Publish, Clean up PDP-Media (#27228)
  • +
  • Add comment-based help documentation to build.psm1 functions (#27227)
  • +
  • Fix a preview detection test for the packaging script (#27226)
  • +
  • Update the PhoneProductId to be the official LTS id used by Store (#27169)
  • +
  • Select New MSIX Package Name (#27173)
  • +
  • Publish .msixbundle package as a VPack (#27187)
  • +
  • Bump github/codeql-action from 4.32.4 to 4.35.1 (#27143) (#27171) (#27175)
  • +
  • release-upload-buildinfo: replace version-comparison channel gating with metadata flags (#27147)
  • +
  • Create infrastructure to create two msixs and msixbundles for LTS and Stable (#27145)
  • +
  • Move _GetDependencies MSBuild target from dynamic generation in build.psm1 into Microsoft.PowerShell.SDK.csproj (#27144)
  • +
  • Bump actions/dependency-review-action from 4.8.3 to 4.9.0 (#27142)
  • +
  • Bump actions/upload-artifact from 6 to 7 (#27141)
  • +
  • Separate Official and NonOfficial templates for ADO pipelines (#27140)
  • +
  • Mirror .NET/runtime ICU version range in PowerShell (#27138)
  • +
+ +
+ +[7.4.15]: https://github.com/PowerShell/PowerShell/compare/v7.4.14...v7.4.15 + +## [7.4.14] + +### General Cmdlet Updates and Fixes + +- Fix `PSMethodInvocationConstraints.GetHashCode` method (#26959) + +### Tools + +- Add merge conflict marker detection to `linux-ci` workflow and refactor existing actions to use reusable `get-changed-files` action (#26362) +- Add reusable `get-changed-files` action and refactor existing actions (#26361) +- Refactor analyze job to reusable workflow and enable on Windows CI (#26342) + +### Tests + +- Skip the flaky `Update-Help` test for the `PackageManagement` module (#26871) +- Fix `$PSDefaultParameterValues` leak causing tests to skip unexpectedly (#26869) +- Add GitHub Actions annotations for Pester test failures (#26800) +- Mark flaky `Update-Help` web tests as pending to unblock CI (#26805) +- Update the `Update-Help` tests to use `-Force` to remove read-only files (#26786) +- Fix merge conflict checker for empty file lists and filter `*.cs` files (#26387) +- Add markdown link verification for PRs (#26340) + +### Build and Packaging Improvements + +
+ + + +

Update .NET SDK to 8.0.419

+ +
+ +
    +
  • Update MaxVisitCount and MaxHashtableKeyCount if visitor safe value context indicates SkipLimitCheck is true (Internal 38882)
  • +
  • Hardcode Official templates (#26962)
  • +
  • Split TPN manifest and Component Governance manifest (#26961)
  • +
  • Correct the package name for .deb and .rpm packages (#26960)
  • +
  • Bring over all changes for MSIX packaging template (#26933)
  • +
  • .NET Resolution and Store Publishing Updates (#26930)
  • +
  • Update Application Insights package version to 2.23.0 (#26883)
  • +
  • Update metadata.json to update the Latest attribute with a better name (#26872)
  • +
  • Update Get-ChangeLog to handle backport PRs correctly (#26870)
  • +
  • Remove unused runCodesignValidationInjection variable from pipeline templates (#26868)
  • +
  • Refactor: Centralize xUnit tests into reusable workflow and remove legacy verification (#26864)
  • +
  • Fix buildinfo.json uploading for preview, LTS, and stable releases (#26863)
  • +
  • Fix macOS preview package identifier detection to use version string (#26774)
  • +
  • Update the macOS package name for preview releases to match the previous pattern (#26435)
  • +
  • Fix condition syntax for StoreBroker package tasks in MSIX pipeline (#26434)
  • +
  • Fix template path for rebuild branch check in package.yml (#26433)
  • +
  • Add rebuild branch support with conditional MSIX signing (#26418)
  • +
  • Move package validation to package pipeline (#26417)
  • +
  • Backport Store publishing improvements (#26401)
  • +
  • Fix path to metadata.json in channel selection script (#26399)
  • +
  • Optimize/split Windows package signing (#26413)
  • +
  • Improve ADO package build and validation across platforms (#26405)
  • +
  • Separate Store Automation Service Endpoints, Resolve AppID (#26396)
  • +
  • Fix the task name to not use the pre-release task (#26395)
  • +
  • Remove usage of fpm for DEB package generation (#26382)
  • +
  • Replace fpm with native macOS packaging tools (pkgbuild/productbuild) (#26344)
  • +
  • Replace fpm with native rpmbuild for RPM package generation (#26337)
  • +
  • Add log grouping to build.psm1 for collapsible GitHub Actions logs (#26363)
  • +
  • Convert Azure DevOps Linux Packaging pipeline to GitHub Actions workflow (#26336)
  • +
  • Integrate Windows packaging into windows-ci workflow using reusable workflow (#26335)
  • +
  • Add network isolation policy parameter to vPack pipeline (#26339)
  • +
  • GitHub Workflow cleanup (#26334)
  • +
  • Add build to vPack Pipeline (#25980)
  • +
  • Update vPack name (#26222)
  • +
+ +
+ +### Documentation and Help Content + +- Update Third Party Notices (#26892) + +[7.4.14]: https://github.com/PowerShell/PowerShell/compare/v7.4.13...v7.4.14 + +## [7.4.13] + +### Build and Packaging Improvements + +
+ + + +

Update .NET SDK to 8.0.415

+ +
+ +
    +
  • [release/v7.4] Update StableRelease to not be the latest (#26042)
  • +
  • [release/v7.4] Update Ev2 Shell Extension Image to AzureLinux 3 for PMC Release (#26033)
  • +
  • [release/v7.4] Add 7.4.12 Changelog (#26018)
  • +
  • [release/v7.4] Fix variable reference for release environment in pipeline (#26014)
  • +
  • Backport Release Pipeline Changes (Internal 37169)
  • +
  • [release/v7.4] Update branch for release (#26194)
  • +
  • [release/v7.4] Mark the 3 consistently failing tests as pending to unblock PRs (#26197)
  • +
  • [release/v7.4] Remove UseDotnet task and use the dotnet-install script (#26170)
  • +
  • [release/v7.4] Automate Store Publishing (#26163)
  • +
  • [release/v7.4] add CodeQL suppresion for NativeCommandProcessor (#26174)
  • +
  • [release/v7.4] add CodeQL suppressions for UpdatableHelp and NativeCommandProcessor methods (#26172)
  • +
  • [release/v7.4] Suppress false positive PSScriptAnalyzer warnings in tests and build scripts (#26058)
  • +
  • [release/v7.4] Ensure that socket timeouts are set only during the token validation (#26080)
  • +
+ +
+ +[7.4.13]: https://github.com/PowerShell/PowerShell/compare/v7.4.12...v7.4.13 + +## [7.4.12] + +### Tools + +- Add CodeQL suppressions (#25973) + +### Build and Packaging Improvements + +
+ + + +

Update .NET SDK to 8.0.413

+ +
+ +
    +
  • Add LinuxHost Network configuration to PowerShell Packages pipeline (#26003)
  • +
  • Update container images to use mcr.microsoft.com for Linux and Azure Linux (#25987)
  • +
  • Update SDK to 8.0.413 (#25993)
  • +
  • Make logical template name consistent between pipelines (#25992)
  • +
  • Remove AsyncSDL from Pipelines Toggle Official/NonOfficial Runs (#25965)
  • +
+ +
+ +### Documentation and Help Content + +- Update third-party library versions to `8.0.19` for `ObjectPool`, Windows Compatibility, and `System.Drawing.Common` (#26001) + +[7.4.12]: https://github.com/PowerShell/PowerShell/compare/v7.4.11...v7.4.12 + ## [7.4.11] - 2025-06-17 ### Engine Updates and Fixes @@ -86,7 +278,7 @@
  • Migrate MacOS Signing to OneBranch (#25412)
  • Remove call to NuGet (#25410)
  • Restore a script needed for build from the old release pipeline cleanup (#25201) (#25408)
  • -
  • Switch to ubuntu-lastest for CI (#25406)
  • +
  • Switch to ubuntu-latest for CI (#25406)
  • Update GitHub Actions to work in private GitHub repository (#25403)
  • Simplify PR Template (#25407)
  • Disable SBOM generation on set variables job in release build (#25341)
  • @@ -252,7 +444,7 @@ _This release is internal only. It is not available for download._
  • Add updated libicu dependency for Debian packages (#24301)
  • Add mapping to azurelinux repo (#24290)
  • Update vpack pipeline (#24281)
  • -
  • Add BaseUrl to buildinfo json file (#24376)
  • +
  • Add BaseUrl to buildinfo JSON file (#24376)
  • Delete the msix blob if it's already there (#24353)
  • Make some release tests run in a hosted pools (#24270)
  • Create new pipeline for compliance (#24252)
  • @@ -855,7 +1047,7 @@ Bump .NET 8 to version 8.0.101
  • Bump StyleCop.Analyzers from 1.2.0-beta.406 to 1.2.0-beta.507 (#19837)
  • Bump Microsoft.CodeAnalysis.CSharp from 4.6.0-1.final to 4.7.0-2.final (#19838)(#19667)
  • Update to .NET 8 Preview 4 (#19696)
  • -
  • Update experimental-feature json files (#19828)
  • +
  • Update experimental-feature JSON files (#19828)
  • Bump JsonSchema.Net from 4.1.1 to 4.1.5 (#19790)(#19768)(#19788)
  • Update group to assign PRs in fabricbot.json (#19759)
  • Add retry on failure for all upload tasks in Azure Pipelines (#19761)
  • @@ -863,7 +1055,7 @@ Bump .NET 8 to version 8.0.101
  • Delete symbols on Linux as well (#19735)
  • Update windows.json packaging BOM (#19728)
  • Disable SBOM signing for CI and add extra files for packaging tests (#19729)
  • -
  • Update experimental-feature json files (#19698(#19588))
  • +
  • Update experimental-feature JSON files (#19698(#19588))
  • Add ProductCode in registry for MSI install (#19590)
  • Runas format changed (#15434) (Thanks @krishnayalavarthi!)
  • For Preview releases, add pwsh-preview.exe alias to MSIX package (#19602)
  • @@ -1024,7 +1216,7 @@ Bump .NET 8 to version 8.0.101 - Build the relative URI for links from the response in `Invoke-WebRequest` (#19092) (Thanks @CarloToso!) - Fix redirection for `-CustomMethod` `POST` in WebCmdlets (#19111) (Thanks @CarloToso!) - Dispose previous response in Webcmdlets (#19117) (Thanks @CarloToso!) -- Improve `Invoke-WebRequest` XML and json errors format (#18837) (Thanks @CarloToso!) +- Improve `Invoke-WebRequest` XML and JSON errors format (#18837) (Thanks @CarloToso!) - Fix error formatting to remove the unneeded leading newline for concise view (#19080) - Add `-NoHeader` parameter to `ConvertTo-Csv` and `Export-Csv` cmdlets (#19108) (Thanks @ArmaanMcleod!) - Fix `Start-Process -Credential -Wait` to work on Windows (#19082) diff --git a/CHANGELOG/7.5.md b/CHANGELOG/7.5.md index 8cf3dba6282..b4f173bcff6 100644 --- a/CHANGELOG/7.5.md +++ b/CHANGELOG/7.5.md @@ -1,5 +1,214 @@ # 7.5 Changelog +## [7.5.6] + +### General Cmdlet Updates and Fixes + +- Delay update notification for one week to ensure all packages become available (#27220) + +### Tests + +- Fix the `PSNativeCommandArgumentPassing` test (#27166) + +### Build and Packaging Improvements + +
    + + + +

    Update to .NET SDK 9.0.313

    + +
    + +
      +
    • Update branch for the v7.5.6 release (#27268)
    • +
    • Fix package pipeline by adding in PDP-Media directory (#27256)
    • +
    • Pin ready-to-merge.yml reusable workflow to commit SHA (#27246)
    • +
    • [StepSecurity] ci: Harden GitHub Actions tags (#27239)
    • +
    • Build, package, and create VPack for the PowerShell-LTS store package within the same msixbundle-vpack pipeline (#27240)
    • +
    • Add comment-based help documentation to build.psm1 functions (#27221)
    • +
    • Separate store package creation, skip polling for store publish, clean up PDP-Media (#27225)
    • +
    • [StepSecurity] ci: Harden GitHub Actions tokens (#27224)
    • +
    • Change the display name of "PowerShell-LTS" package to "PowerShell LTS" (#27223)
    • +
    • Redo windows image fix to use latest image (#27222)
    • +
    • Bump github/codeql-action from 4.32.4 to 4.35.1 (#27159) (#27170) (#27174)
    • +
    • Select new MSIX package name (#27172)
    • +
    • Update the PhoneProductId to be the official LTS id used by Store (#27168)
    • +
    • release-upload-buildinfo: replace version-comparison channel gating with metadata flags (#27167)
    • +
    • Create infrastructure to create two msixs and msixbundles for LTS and Stable (#27165)
    • +
    • Move _GetDependencies MSBuild target from dynamic generation in build.psm1 into Microsoft.PowerShell.SDK.csproj (#27164)
    • +
    • Create Linux LTS deb/rpm packages for LTS releases (#27163)
    • +
    • Fix the container image for vPack, MSIX vPack and Package pipelines (#27161)
    • +
    • Create LTS pkg and non-LTS pkg for macOS for LTS releases (#27162)
    • +
    • Bump actions/dependency-review-action from 4.8.3 to 4.9.0 (#27158)
    • +
    • Bump actions/upload-artifact from 6 to 7 (#27157)
    • +
    • Separate "Official" and "NonOfficial" templates for ADO pipelines (#27155)
    • +
    + +
    + +[7.5.6]: https://github.com/PowerShell/PowerShell/compare/v7.5.5...v7.5.6 + +## [7.5.5] + +### Engine Updates and Fixes + +- Fix up `SSHConnectionInfo` ssh PATH checks (#26165) (Thanks @jborean93!) + +### General Cmdlet Updates and Fixes + +- Close pipe client handles after creating the child ssh process (#26822) +- Fix the progress preference variable in script cmdlets (#26791) (Thanks @cmkb3!) + +### Tools + +- Add merge conflict marker detection to `linux-ci` workflow and refactor existing actions to use reusable `get-changed-files` action (#26812) +- Add reusable `get-changed-files` action and refactor existing actions (#26811) +- Create GitHub Copilot setup workflow (#26807) +- Refactor analyze job to reusable workflow and enable on Windows CI (#26799) + +### Tests + +- Mark flaky `Update-Help` web tests as pending to unblock CI (#26837) +- Add GitHub Actions annotations for Pester test failures (#26836) +- Fix `$PSDefaultParameterValues` leak causing tests to skip unexpectedly (#26823) +- Fix merge conflict checker for empty file lists and filter `*.cs` files (#26813) +- Update the `Update-Help` tests to use `-Force` to remove read-only files (#26788) +- Add markdown link verification for PRs (#26407) + +### Build and Packaging Improvements + +
    + + +

    Update to .NET SDK 9.0.312

    +

    We thank the following contributors!

    +

    @kasperk81, @RichardSlater

    + +
    + +
      +
    • Revert change to module name ThreadJob (#26997)
    • +
    • Update branch for release (#26990)
    • +
    • Fix ConvertFrom-ClearlyDefinedCoordinates to handle API object coordinates (#26987)
    • +
    • Update CGManifests (#26981)
    • +
    • Hardcode Official templates (#26968)
    • +
    • Split TPN manifest and Component Governance manifest (#26967)
    • +
    • Fix a preview detection test for the packaging script (#26966)
    • +
    • Correct the package name for .deb and .rpm packages (#26964)
    • +
    • Bring Release Changes from v7.6.0-preview.6 (#26963)
    • +
    • Merge the v7.6.0-preview.5 release branch back to master (#26958)
    • +
    • Fix macOS preview package identifier detection to use version string (#26835)
    • +
    • Update metadata.json to update the Latest attribute with a better name (#26826)
    • +
    • Remove unused runCodesignValidationInjection variable from pipeline templates (#26825)
    • +
    • Update Get-ChangeLog to handle backport PRs correctly (#26824)
    • +
    • Mirror .NET/runtime ICU version range in PowerShell (#26821) (Thanks @kasperk81!)
    • +
    • Update the macos package name for preview releases to match the previous pattern (#26820)
    • +
    • Fix condition syntax for StoreBroker package tasks in MSIX pipeline (#26819)
    • +
    • Fix template path for rebuild branch check in package.yml (#26818)
    • +
    • Add rebuild branch support with conditional MSIX signing (#26817)
    • +
    • Move package validation to package pipeline (#26816)
    • +
    • Optimize/split windows package signing (#26815)
    • +
    • Improve ADO package build and validation across platforms (#26814)
    • +
    • Add log grouping to build.psm1 for collapsible GitHub Actions logs (#26810)
    • +
    • Remove usage of fpm for DEB package generation (#26809)
    • +
    • Replace fpm with native macOS packaging tools (pkgbuild/productbuild) (#26801)
    • +
    • Fix build to only enable ready-to-run for the Release configuration (#26798)
    • +
    • Fix R2R for fxdependent packaging (#26797)
    • +
    • Refactor: Centralize xUnit tests into reusable workflow and remove legacy verification (#26794)
    • +
    • Replace fpm with native rpmbuild for RPM package generation (#26793)
    • +
    • Add libicu76 dependency to support Debian 13 (#26792) (Thanks @RichardSlater!)
    • +
    • Specify .NET search by build type (#26408)
    • +
    • Fix buildinfo.json uploading for preview, LTS, and stable releases (#26773)
    • +
    • Fix path to metadata.json in channel selection script (#26400)
    • +
    • Separate store automation service endpoints and resolve AppID (#26266)
    • +
    • Update a few packages to use the right version corresponding to .NET 9 (#26671)
    • +
    • Add network isolation policy parameter to vPack pipeline (#26393)
    • +
    • Convert Azure DevOps Linux Packaging pipeline to GitHub Actions workflow (#26391)
    • +
    • Integrate Windows packaging into windows-ci workflow using reusable workflow (#26390)
    • +
    • GitHub Workflow cleanup (#26389)
    • +
    • Update vPack name (#26221)
    • +
    + +
    + +[7.5.5]: https://github.com/PowerShell/PowerShell/compare/v7.5.4...v7.5.5 + +## [7.5.4] + +### Build and Packaging Improvements + +
    + + + +

    Update to .NET SDK 9.0.306

    + +
    + +
      +
    • [release/v7.5] Update Ev2 Shell Extension Image to AzureLinux 3 for PMC Release (#26032)
    • +
    • [release/v7.5] Fix variable reference for release environment in pipeline (#26013)
    • +
    • [release/v7.5] Add v7.5.3 Changelog (#26015)
    • +
    • [release/v7.5] Add LinuxHost Network configuration to PowerShell Packages pipeline (#26002)
    • +
    • Backport Release Pipeline Changes (Internal 37168)
    • +
    • [release/v7.5] Update branch for release (#26195)
    • +
    • [release/v7.5] Mark the 3 consistently failing tests as pending to unblock PRs (#26196)
    • +
    • [release/v7.5] add CodeQL suppresion for NativeCommandProcessor (#26173)
    • +
    • [release/v7.5] add CodeQL suppressions for UpdatableHelp and NativeCommandProcessor methods (#26171)
    • +
    • [release/v7.5] Remove UseDotnet task and use the dotnet-install script (#26169)
    • +
    • [release/v7.5] Automate Store Publishing (#26164)
    • +
    • [release/v7.5] Ensure that socket timeouts are set only during the token validation (#26079)
    • +
    • [release/v7.5] Suppress false positive PSScriptAnalyzer warnings in tests and build scripts (#26059)
    • +
    + +
    + +[7.5.4]: https://github.com/PowerShell/PowerShell/compare/v7.5.3...v7.5.4 + + +## [7.5.3] + +### General Cmdlet Updates and Fixes + +- Fix `Out-GridView` by replacing the use of obsolete `BinaryFormatter` with custom implementation. (#25559) +- Remove `OnDeserialized` and `Serializable` attributes from `Microsoft.Management.UI.Internal` project (#25831) +- Make the interface `IDeepCloneable` internal (#25830) + +### Tools + +- Add CodeQL suppressions (#25972) + +### Tests + +- Fix updatable help test for new content (#25944) + +### Build and Packaging Improvements + +
    + + + +

    Update to .NET SDK 9.0.304

    + +
    + +
      +
    • Make logical template name consistent between pipelines (#25991)
    • +
    • Update container images to use mcr.microsoft.com for Linux and Azure Linux (#25986)
    • +
    • Add build to vPack Pipeline (#25975)
    • +
    • Remove AsyncSDL from Pipelines Toggle Official/NonOfficial Runs (#25964)
    • +
    • Update branch for release (#25942)
    • +
    + +
    + +### Documentation and Help Content + +- Fix typo in CHANGELOG for script filename suggestion (#25963) + +[7.5.3]: https://github.com/PowerShell/PowerShell/compare/v7.5.2...v7.5.3 + ## [7.5.2] - 2025-06-24 ### Engine Updates and Fixes @@ -10,7 +219,7 @@ - Set standard handles explicitly when starting a process with `-NoNewWindow` (#25324) - Make inherited protected internal instance members accessible in class scope. (#25547) (Thanks @mawosoft!) -- Remove the old fuzzy suggestion and fix the local script file name suggestion (#25330) +- Remove the old fuzzy suggestion and fix the local script filename suggestion (#25330) - Fix `PSMethodInvocationConstraints.GetHashCode` method (#25306) (Thanks @crazyjncsu!) ### Build and Packaging Improvements @@ -41,7 +250,6 @@ [7.5.2]: https://github.com/PowerShell/PowerShell/compare/v7.5.1...v7.5.2 - ## [7.5.1] ### Engine Updates and Fixes diff --git a/CHANGELOG/7.6.md b/CHANGELOG/7.6.md new file mode 100644 index 00000000000..cd09b76272c --- /dev/null +++ b/CHANGELOG/7.6.md @@ -0,0 +1,852 @@ +# 7.6 Changelog + +## [7.6.1] + +### General Cmdlet Updates and Fixes + +- Delay update notification for one week to ensure all packages become available (#27215) + +### Tests + +- Fix the `PSNativeCommandArgumentPassing` test (#27179) + +### Build and Packaging Improvements + +
    + + + +

    Update to .NET SDK 10.0.202

    + +
    + +
      +
    • Fix PMC Repo URL for RHEL10 (#27061) (#27062)
    • +
    • Update branch for release (#27287)
    • +
    • Fix package pipeline by adding in PDP-Media directory (#27257)
    • +
    • Pin ready-to-merge.yml reusable workflow to commit SHA (#27245)
    • +
    • [StepSecurity] ci: Harden GitHub Actions tags (#27236)
    • +
    • Build, package, and create VPack for the PowerShell-LTS store package within the same msixbundle-vpack pipeline (#27237)
    • +
    • Change the display name of PowerShell-LTS package to PowerShell LTS (#27219)
    • +
    • [StepSecurity] ci: Harden GitHub Actions tokens (#27218)
    • +
    • Redo windows image fix to use latest image (#27217)
    • +
    • Add comment-based help documentation to build.psm1 functions (#27216)
    • +
    • Separate Store Package Creation, Skip Polling for Store Publish, Clean up PDP-Media (#27214)
    • +
    • Bump github/codeql-action from 4.34.1 to 4.35.1 (#27184)
    • +
    • Bump github/codeql-action from 4.32.6 to 4.34.1 (#27182)
    • +
    • Select New MSIX Package Name (#27183)
    • +
    • Update the PhoneProductId to be the official LTS id used by Store (#27181)
    • +
    • release-upload-buildinfo: replace version-comparison channel gating with metadata flags (#27180)
    • +
    • Move _GetDependencies MSBuild target from dynamic generation in build.psm1 into Microsoft.PowerShell.SDK.csproj (#27177)
    • +
    • Separate Official and NonOfficial templates for ADO pipelines (#27176)
    • +
    + +
    + +[7.6.1]: https://github.com/PowerShell/PowerShell/compare/v7.6.0...v7.6.1 + +## [7.6.0] + +### General Cmdlet Updates and Fixes + +- Update PowerShell Profile DSC resource manifests to allow `null` for content (#26973) + +### Tests + +- Add GitHub Actions annotations for Pester test failures (#26969) +- Fix `Import-Module.Tests.ps1` to handle Arm32 platform (#26888) + +### Build and Packaging Improvements + +
    + + + +

    Update to .NET SDK 10.0.201

    + +
    + +
      +
    • Update v7.6 release branch to use .NET SDK 10.0.201 (#27041)
    • +
    • Create LTS package and non-LTS package for macOS for LTS releases (#27040)
    • +
    • Fix the container image for package pipelines (#27020)
    • +
    • Update Microsoft.PowerShell.PSResourceGet version to 1.2.0 (#27007)
    • +
    • Update LTS and Stable release settings in metadata (#27006)
    • +
    • Update branch for release (#26989)
    • +
    • Fix ConvertFrom-ClearlyDefinedCoordinates to handle API object coordinates (#26986)
    • +
    • Update NuGet package versions in cgmanifest.json to actually match the branch (#26982)
    • +
    • Bump actions/upload-artifact from 6 to 7 (#26979)
    • +
    • Split TPN manifest and Component Governance manifest (#26978)
    • +
    • Bump github/codeql-action from 4.32.4 to 4.32.6 (#26975)
    • +
    • Bump actions/dependency-review-action from 4.8.3 to 4.9.0 (#26974)
    • +
    • Hardcode Official templates (#26972)
    • +
    • Fix a preview detection test for the packaging script (#26971)
    • +
    • Add PMC packages for debian13 and rhel10 (#26917)
    • +
    • Add version in description and pass store task on failure (#26889)
    • +
    • Exclude .exe packages from publishing to GitHub (#26887)
    • +
    • Correct the package name for .deb and .rpm packages (#26884)
    • +
    + +
    + +[7.6.0]: https://github.com/PowerShell/PowerShell/compare/v7.6.0-rc.1...v7.6.0 + +## [7.6.0-rc.1] - 2026-02-19 + +### Tests + +- Fix `$PSDefaultParameterValues` leak causing tests to skip unexpectedly (#26705) + +### Build and Packaging Improvements + +
    + + + +

    Expand to see details.

    + +
    + +
      +
    • Update branch for release (#26779)
    • +
    • Update Microsoft.PowerShell.PSResourceGet version to 1.2.0-rc3 (#26767)
    • +
    • Update Microsoft.PowerShell.Native package version (#26748)
    • +
    • Move PowerShell build to depend on .NET SDK 10.0.102 (#26717)
    • +
    • Fix buildinfo.json uploading for preview, LTS, and stable releases (#26715)
    • +
    • Fix macOS preview package identifier detection to use version string (#26709)
    • +
    • Update metadata.json to update the Latest attribute with a better name (#26708)
    • +
    • Remove unused runCodesignValidationInjection variable from pipeline templates (#26707)
    • +
    • Update Get-ChangeLog to handle backport PRs correctly (#26706)
    • +
    • Bring release changes from the v7.6.0-preview.6 release (#26626)
    • +
    • Fix the DSC test by skipping AfterAll cleanup if the initial setup in BeforeAll failed (#26622)
    • +
    + +
    + +[7.6.0-rc.1]: https://github.com/PowerShell/PowerShell/compare/v7.6.0-preview.6...v7.6.0-rc.1 + +## [7.6.0-preview.6] - 2025-12-11 + +### Engine Updates and Fixes + +- Properly Expand Aliases to their actual ResolvedCommand (#26571) (Thanks @kilasuit!) + +### General Cmdlet Updates and Fixes + +- Update `Microsoft.PowerShell.PSResourceGet` to `v1.2.0-preview5` (#26590) +- Make the experimental feature `PSFeedbackProvider` stable (#26502) +- Fix a regression in the API `CompletionCompleters.CompleteFilename()` that causes null reference exception (#26487) +- Add Delimiter parameter to `Get-Clipboard` (#26572) (Thanks @MartinGC94!) +- Close pipe client handles after creating the child ssh process (#26564) +- Make some experimental features stable (#26490) +- DSC v3 resource for PowerShell Profile (#26447) + +### Tools + +- Add merge conflict marker detection to linux-ci workflow and refactor existing actions to use reusable get-changed-files action (#26530) +- Add reusable get-changed-files action and refactor existing actions (#26529) +- Refactor analyze job to reusable workflow and enable on Windows CI (#26494) + +### Tests + +- Fix merge conflict checker for empty file lists and filter *.cs files (#26556) +- Add markdown link verification for PRs (#26445) + +### Build and Packaging Improvements + +
    + + + +

    Expand to see details.

    + +
    + +
      +
    • Fix template path for rebuild branch check in package.yml (#26560)
    • +
    • Update the macos package name for preview releases to match the previous pattern (#26576)
    • +
    • Add rebuild branch support with conditional MSIX signing (#26573)
    • +
    • Update the WCF packages to the latest version that is compatible with v4.10.3 (#26503)
    • +
    • Improve ADO package build and validation across platforms (#26532)
    • +
    • Mirror .NET/runtime ICU version range in PowerShell (#26563) (Thanks @kasperk81!)
    • +
    • Update the macos package name for preview releases to match the previous pattern (#26562)
    • +
    • Fix condition syntax for StoreBroker package tasks in MSIX pipeline (#26561)
    • +
    • Move package validation to package pipeline (#26558)
    • +
    • Optimize/split windows package signing (#26557)
    • +
    • Remove usage of fpm for DEB package generation (#26504)
    • +
    • Add log grouping to build.psm1 for collapsible GitHub Actions logs (#26524)
    • +
    • Replace fpm with native macOS packaging tools (pkgbuild/productbuild) (#26501)
    • +
    • Replace fpm with native rpmbuild for RPM package generation (#26441)
    • +
    • Fix GitHub API rate limit errors in test actions (#26492)
    • +
    • Convert Azure DevOps Linux Packaging pipeline to GitHub Actions workflow (#26493)
    • +
    • Refactor: Centralize xUnit tests into reusable workflow and remove legacy verification (#26488)
    • +
    • Fix build to only enable ready-to-run for the Release configuration (#26481)
    • +
    • Integrate Windows packaging into windows-ci workflow using reusable workflow (#26468)
    • +
    • Update outdated package references (#26471)
    • +
    • GitHub Workflow cleanup (#26439)
    • +
    • Update PSResourceGet package version to preview4 (#26438)
    • +
    • Update PSReadLine to v2.4.5 (#26446)
    • +
    • Add network isolation policy parameter to vPack pipeline (#26444)
    • +
    • Fix a couple more lint errors
    • +
    • Fix lint errors in preview.md
    • +
    • Make MSIX publish stage dependent on SetReleaseTagandContainerName stage
    • +
    + +
    + +[7.6.0-preview.6]: https://github.com/PowerShell/PowerShell/compare/v7.6.0-preview.5...v7.6.0-preview.6 + +## [7.6.0-preview.5] - 2025-09-30 + +### Engine Updates and Fixes + +- Allow opt-out of the named-pipe listener using the environment variable `POWERSHELL_DIAGNOSTICS_OPTOUT` (#26086) +- Ensure that socket timeouts are set only during the token validation (#26066) +- Fix race condition in `RemoteHyperVSocket` (#26057) +- Fix `stderr` output of console host to respect `NO_COLOR` (#24391) +- Update PSRP protocol to deprecate session key exchange between newer client and server (#25774) +- Fix the `ssh` PATH check in `SSHConnectionInfo` when the default Runspace is not available (#25780) (Thanks @jborean93!) +- Adding hex format for native command exit codes (#21067) (Thanks @sba923!) +- Fix infinite loop crash in variable type inference (#25696) (Thanks @MartinGC94!) +- Add `PSForEach` and `PSWhere` as aliases for the PowerShell intrinsic methods `Where` and `Foreach` (#25511) (Thanks @powercode!) + +### General Cmdlet Updates and Fixes + +- Remove `IsScreenReaderActive()` check from `ConsoleHost` (#26118) +- Fix `ConvertFrom-Json` to ignore comments inside array literals (#14553) (#26050) (Thanks @MatejKafka!) +- Fix `-Debug` to not trigger the `ShouldProcess` prompt (#26081) +- Add the parameter `Register-ArgumentCompleter -NativeFallback` to support registering a cover-all completer for native commands (#25230) +- Change the default feedback provider timeout from 300ms to 1000ms (#25910) +- Update PATH environment variable for package manager executable on Windows (#25847) +- Fix `Write-Host` to respect `OutputRendering = PlainText` (#21188) +- Improve the `$using` expression support in `Invoke-Command` (#24025) (Thanks @jborean93!) +- Use parameter `HelpMessage` for tool tip in parameter completion (#25108) (Thanks @jborean93!) +- Revert "Never load a module targeting the PSReadLine module's `SessionState`" (#25792) +- Fix debug tracing error with magic extents (#25726) (Thanks @jborean93!) +- Add `MethodInvocation` trace for overload tracing (#21320) (Thanks @jborean93!) +- Improve verbose and debug logging level messaging in web cmdlets (#25510) (Thanks @JustinGrote!) +- Fix quoting in completion if the path includes a double quote character (#25631) (Thanks @MartinGC94!) +- Fix the common parameter `-ProgressAction` for advanced functions (#24591) (Thanks @cmkb3!) +- Use absolute path in `FileSystemProvider.CreateDirectory` (#24615) (Thanks @Tadas!) +- Make inherited protected internal instance members accessible in PowerShell class scope (#25245) (Thanks @mawosoft!) +- Treat `-Target` as literal in `New-Item` (#25186) (Thanks @GameMicrowave!) +- Remove duplicate modules from completion results (#25538) (Thanks @MartinGC94!) +- Add completion for variables assigned in `ArrayLiteralAst` and `ParenExpressionAst` (#25303) (Thanks @MartinGC94!) +- Add support for thousands separators in `[bigint]` casting (#25396) (Thanks @AbishekPonmudi!) +- Add internal methods to check Preferences (#25514) (Thanks @iSazonov!) +- Improve debug logging of Web cmdlet request and response (#25479) (Thanks @JustinGrote!) +- Revert "Allow empty prefix string in 'Import-Module -Prefix' to override default prefix in manifest (#20409)" (#25462) (Thanks @MartinGC94!) +- Fix the `NullReferenceException` when writing progress records to console from multiple threads (#25440) (Thanks @kborowinski!) +- Update `Get-Service` to ignore common errors when retrieving non-critical properties for a service (#24245) (Thanks @jborean93!) +- Add single/double quote support for `Join-String` Argument Completer (#25283) (Thanks @ArmaanMcleod!) +- Fix tab completion for env/function variables (#25346) (Thanks @jborean93!) +- Fix `Out-GridView` by replacing use of obsolete `BinaryFormatter` with custom implementation (#25497) (Thanks @mawosoft!) +- Remove the use of Windows PowerShell ETW provider ID from codebase and update the `PSDiagnostics` module to work for PowerShell 7 (#25590) + +### Code Cleanup + +
    + + + +

    We thank the following contributors!

    +

    @xtqqczze, @mawosoft, @ArmaanMcleod

    + +
    + +
      +
    • Enable CA2021: Do not call Enumerable.Cast or Enumerable.OfType with incompatible types (#25813) (Thanks @xtqqczze!)
    • +
    • Remove some unused ConsoleControl structs (#26063) (Thanks @xtqqczze!)
    • +
    • Remove unused FileStreamBackReader.NativeMethods type (#26062) (Thanks @xtqqczze!)
    • +
    • Ensure data-serialization files end with one newline (#26039) (Thanks @xtqqczze!)
    • +
    • Remove unnecessary CS0618 suppressions from Variant APIs (#26006) (Thanks @xtqqczze!)
    • +
    • Ensure .cs files end with exactly one newline (#25968) (Thanks @xtqqczze!)
    • +
    • Remove obsolete CA2105 rule suppression (#25938) (Thanks @xtqqczze!)
    • +
    • Remove obsolete CA1703 rule suppression (#25955) (Thanks @xtqqczze!)
    • +
    • Remove obsolete CA2240 rule suppression (#25957) (Thanks @xtqqczze!)
    • +
    • Remove obsolete CA1701 rule suppression (#25948) (Thanks @xtqqczze!)
    • +
    • Remove obsolete CA2233 rule suppression (#25951) (Thanks @xtqqczze!)
    • +
    • Remove obsolete CA1026 rule suppression (#25934) (Thanks @xtqqczze!)
    • +
    • Remove obsolete CA1059 rule suppression (#25940) (Thanks @xtqqczze!)
    • +
    • Remove obsolete CA2118 rule suppression (#25924) (Thanks @xtqqczze!)
    • +
    • Remove redundant System.Runtime.Versioning attributes (#25926) (Thanks @xtqqczze!)
    • +
    • Seal internal types in Microsoft.PowerShell.Commands.Utility (#25892) (Thanks @xtqqczze!)
    • +
    • Seal internal types in Microsoft.PowerShell.Commands.Management (#25849) (Thanks @xtqqczze!)
    • +
    • Make the interface IDeepCloneable internal to minimize confusion (#25552)
    • +
    • Remove OnDeserialized and Serializable attributes from Microsoft.Management.UI.Internal project (#25548)
    • +
    • Refactor Tooltip/ListItemText mapping to use CompletionDisplayInfoMapper delegate (#25395) (Thanks @ArmaanMcleod!)
    • +
    + +
    + +### Tools + +- Add Codeql Suppressions (#25943, #26132) +- Update CODEOWNERS to add Justin as a maintainer (#25386) +- Do not run labels workflow in the internal repository (#25279) + +### Tests + +- Mark the 3 consistently failing tests as pending to unblock PRs (#26091) +- Make some tests less noisy on failure (#26035) (Thanks @xtqqczze!) +- Suppress false positive `PSScriptAnalyzer` warnings in tests and build scripts (#25864) +- Fix updatable help test for new content (#25819) +- Add more tests for `PSForEach` and `PSWhere` methods (#25519) +- Fix the isolated module test that was disabled previously (#25420) + +### Build and Packaging Improvements + +
    + + + +

    We thank the following contributors!

    +

    @alerickson, @senerh, @RichardSlater, @xtqqczze

    + +
    + +
      +
    • Update package references for the master branch (#26124)
    • +
    • Remove ThreadJob module and update PSReadLine to 2.4.4-beta4 (#26120)
    • +
    • Automate Store Publishing (#25725)
    • +
    • Add global config change detection to action (#26082)
    • +
    • Update outdated package references (#26069)
    • +
    • Ensure that the workflows are triggered on .globalconfig and other files at the root of the repo (#26034)
    • +
    • Update Microsoft.PowerShell.PSResourceGet to 1.2.0-preview3 (#26056) (Thanks @alerickson!)
    • +
    • Update metadata for Stable to v7.5.3 and LTS to v7.4.12 (#26054) (Thanks @senerh!)
    • +
    • Bump github/codeql-action from 3.30.2 to 3.30.3 (#26036)
    • +
    • Update version for the package Microsoft.PowerShell.Native (#26041)
    • +
    • Fix the APIScan pipeline (#26016)
    • +
    • Move PowerShell build to use .NET SDK 10.0.100-rc.1 (#26027)
    • +
    • fix(apt-package): add libicu76 dependency to support Debian 13 (#25866) (Thanks @RichardSlater!)
    • +
    • Bump github/codeql-action from 3.30.1 to 3.30.2 (#26029)
    • +
    • Update Ev2 Shell Extension Image to AzureLinux 3 for PMC Release (#26025)
    • +
    • Bump github/codeql-action from 3.30.0 to 3.30.1 (#26008)
    • +
    • Bump actions/github-script from 7 to 8 (#25983)
    • +
    • Fix variable reference for release environment in pipeline (#26012)
    • +
    • Add LinuxHost Network configuration to PowerShell Packages pipeline (#26000)
    • +
    • Make logical template name consistent between pipelines (#25990)
    • +
    • Update container images to use mcr.microsoft.com for Linux and Azure GǪ (#25981)
    • +
    • Bump github/codeql-action from 3.29.11 to 3.30.0 (#25966)
    • +
    • Bump actions/setup-dotnet from 4 to 5 (#25978)
    • +
    • Add build to vPack Pipeline (#25915)
    • +
    • Replace DOTNET_SKIP_FIRST_TIME_EXPERIENCE with DOTNET_NOLOGO (#25946) (Thanks @xtqqczze!)
    • +
    • Bump actions/dependency-review-action from 4.7.2 to 4.7.3 (#25930)
    • +
    • Bump github/codeql-action from 3.29.10 to 3.29.11 (#25889)
    • +
    • Remove AsyncSDL from Pipelines Toggle Official/NonOfficial Runs (#25885)
    • +
    • Specify .NET Search by Build Type (#25837)
    • +
    • Update PowerShell to use .NET SDK v10-preview.7 (#25876)
    • +
    • Bump actions/dependency-review-action from 4.7.1 to 4.7.2 (#25882)
    • +
    • Bump github/codeql-action from 3.29.9 to 3.29.10 (#25881)
    • +
    • Change the macos runner image to macos 15 large (#25867)
    • +
    • Bump actions/checkout from 4 to 5 (#25853)
    • +
    • Bump github/codeql-action from 3.29.7 to 3.29.9 (#25857)
    • +
    • Update to .NET 10 Preview 6 (#25828)
    • +
    • Bump agrc/create-reminder-action from 1.1.20 to 1.1.22 (#25808)
    • +
    • Bump agrc/reminder-action from 1.0.17 to 1.0.18 (#25807)
    • +
    • Bump github/codeql-action from 3.28.19 to 3.29.5 (#25797)
    • +
    • Bump super-linter/super-linter from 7.4.0 to 8.0.0 (#25770)
    • +
    • Update metadata for v7.5.2 and v7.4.11 releases (#25687)
    • +
    • Correct Capitalization Referencing Templates (#25669)
    • +
    • Change linux packaging tests to ubuntu latest (#25634)
    • +
    • Bump github/codeql-action from 3.28.18 to 3.28.19 (#25636)
    • +
    • Move to .NET 10 preview 4 and update package references (#25602)
    • +
    • Revert "Add windows signing for pwsh.exe" (#25586)
    • +
    • Bump ossf/scorecard-action from 2.4.1 to 2.4.2 (#25628)
    • +
    • Publish .msixbundle package as a VPack (#25612)
    • +
    • Bump agrc/reminder-action from 1.0.16 to 1.0.17 (#25573)
    • +
    • Bump agrc/create-reminder-action from 1.1.18 to 1.1.20 (#25572)
    • +
    • Bump github/codeql-action from 3.28.17 to 3.28.18 (#25580)
    • +
    • Bump super-linter/super-linter from 7.3.0 to 7.4.0 (#25563)
    • +
    • Bump actions/dependency-review-action from 4.7.0 to 4.7.1 (#25562)
    • +
    • Update metadata.json with 7.4.10 (#25554)
    • +
    • Bump github/codeql-action from 3.28.16 to 3.28.17 (#25508)
    • +
    • Bump actions/dependency-review-action from 4.6.0 to 4.7.0 (#25529)
    • +
    • Move MSIXBundle to Packages and Release to GitHub (#25512)
    • +
    • Update outdated package references (#25506)
    • +
    • Bump github/codeql-action from 3.28.15 to 3.28.16 (#25429)
    • +
    • Fix Conditional Parameter to Skip NuGet Publish (#25468)
    • +
    • Update metadata.json (#25438)
    • +
    • Fix MSIX artifact upload, vPack template, changelog hashes, git tag command (#25437)
    • +
    • Use new variables template for vPack (#25434)
    • +
    • Bump agrc/create-reminder-action from 1.1.17 to 1.1.18 (#25416)
    • +
    • Add PSScriptAnalyzer (#25423)
    • +
    • Update outdated package references (#25392)
    • +
    • Use GitHubReleaseTask instead of custom script (#25398)
    • +
    • Update APIScan to use new symbols server (#25388)
    • +
    • Retry ClearlyDefined operations (#25385)
    • +
    • Update to .NET 10.0.100-preview.3 (#25358)
    • +
    • Enhance path filters action to set outputs for all changes when not a PR (#25367)
    • +
    • Combine GitHub and Nuget Release Stage (#25318)
    • +
    • Add Windows Store Signing to MSIX bundle (#25296)
    • +
    • Bump skitionek/notify-microsoft-teams from 190d4d92146df11f854709774a4dae6eaf5e2aa3 to e7a2493ac87dad8aa7a62f079f295e54ff511d88 (#25366)
    • +
    • Add CodeQL suppressions for PowerShell intended behavior (#25359)
    • +
    • Migrate MacOS Signing to OneBranch (#25295)
    • +
    • Bump github/codeql-action from 3.28.13 to 3.28.15 (#25290)
    • +
    • Update test result processing to use NUnitXml format and enhance logging for better clarity (#25288)
    • +
    • Fix R2R for fxdependent packaging (#26131)
    • +
    • Remove UseDotnet task and use the dotnet-install script (#26093)
    • +
    + +
    + +### Documentation and Help Content + +- Fix a typo in the 7.4 changelog (#26038) (Thanks @VbhvGupta!) +- Add 7.4.12 changelog (#26011) +- Add v7.5.3 changelog (#25994) +- Fix typo in changelog for script filename suggestion (#25962) +- Update changelog for v7.5.2 (#25668) +- Update changelog for v7.4.11 (#25667) +- Update build documentation with instruction of dev terminal (#25587) +- Update links and contribution guide in documentation (#25532) (Thanks @JustinGrote!) +- Add 7.4.10 changelog (#25520) +- Add 7.5.1 changelog (#25382) + +[7.6.0-preview.5]: https://github.com/PowerShell/PowerShell/compare/v7.6.0-preview.4...v7.6.0-preview.5 + +## [7.6.0-preview.4] + +### Breaking Changes + +- Fix `WildcardPattern.Escape` to escape lone backticks correctly (#25211) (Thanks @ArmaanMcleod!) +- Convert `-ChildPath` parameter to `string[]` for `Join-Path` cmdlet (#24677) (Thanks @ArmaanMcleod!) + +PowerShell 7.6-preview.4 includes the following updated modules: + +- **Microsoft.PowerShell.ThreadJob** v2.2.0 +- **ThreadJob** v2.1.0 +The **ThreadJob** module was renamed to **Microsoft.PowerShell.ThreadJob**. There is no difference +in the functionality of the module. To ensure backward compatibility for scripts that use the old +name, the **ThreadJob** v2.1.0 module is a proxy module that points to the +**Microsoft.PowerShell.ThreadJob** v2.2.0. + +### Engine Updates and Fixes + +- Add `PipelineStopToken` to `Cmdlet` which will be signaled when the pipeline is stopping (#24620) (Thanks @jborean93!) +- Fallback to AppLocker after `WldpCanExecuteFile` (#24912) +- Move .NET method invocation logging to after the needed type conversion is done for method arguments (#25022) +- Fix share completion with provider and spaces (#19440) (Thanks @MartinGC94!) + +### General Cmdlet Updates and Fixes + +- Exclude `-OutVariable` assignments within the same `CommandAst` when inferring variables (#25224) (Thanks @MartinGC94!) +- Fix infinite loop in variable type inference (#25206) (Thanks @MartinGC94!) +- Update `Microsoft.PowerShell.PSResourceGet` version in `PSGalleryModules.csproj` (#25135) +- Add tooltips for hashtable key completions (#17864) (Thanks @MartinGC94!) +- Fix type inference of parameters in classic functions (#25172) (Thanks @MartinGC94!) +- Improve assignment type inference (#21143) (Thanks @MartinGC94!) +- Fix `TypeName.GetReflectionType()` to work when the `TypeName` instance represents a generic type definition within a `GenericTypeName` (#24985) +- Remove the old fuzzy suggestion and fix the local script filename suggestion (#25177) +- Improve variable type inference (#19830) (Thanks @MartinGC94!) +- Fix parameter completion when script requirements fail (#17687) (Thanks @MartinGC94!) +- Improve the completion for attribute arguments (#25129) (Thanks @MartinGC94!) +- Fix completion that relies on pseudobinding in script blocks (#25122) (Thanks @MartinGC94!) +- Don't complete duplicate command names (#21113) (Thanks @MartinGC94!) +- Make `SystemPolicy` public APIs visible but non-op on Unix platforms so that they can be included in `PowerShellStandard.Library` (#25051) +- Set standard handles explicitly when starting a process with `-NoNewWindow` (#25061) +- Fix tooltip for variable expansion and include desc (#25112) (Thanks @jborean93!) +- Add type inference for functions without OutputType attribute and anonymous functions (#21127) (Thanks @MartinGC94!) +- Add completion for variables assigned by command redirection (#25104) (Thanks @MartinGC94!) +- Handle type inference for redirected commands (#21131) (Thanks @MartinGC94!) +- Allow empty prefix string in `Import-Module -Prefix` to override default prefix in manifest (#20409) (Thanks @MartinGC94!) +- Update variable/property assignment completion so it can fallback to type inference (#21134) (Thanks @MartinGC94!) +- Use `Get-Help` approach to find `about_*.help.txt` files with correct locale for completions (#24194) (Thanks @MartinGC94!) +- Use script filepath when completing relative paths for using statements (#20017) (Thanks @MartinGC94!) +- Fix completion of variables assigned inside Do loops (#25076) (Thanks @MartinGC94!) +- Fix completion of provider paths when a path returns itself instead of its children (#24755) (Thanks @MartinGC94!) +- Enable completion of scoped variables without specifying scope (#20340) (Thanks @MartinGC94!) +- Fix issue with incomplete results when completing paths with wildcards in non-filesystem providers (#24757) (Thanks @MartinGC94!) +- Allow DSC parsing through OS architecture translation layers (#24852) (Thanks @bdeb1337!) + +### Code Cleanup + +
    + + + +

    We thank the following contributors!

    +

    @ArmaanMcleod, @pressRtowin

    + +
    + +
      +
    • Refactor and add comments to CompletionRequiresQuotes to clarify implementation (#25223) (Thanks @ArmaanMcleod!)
    • +
    • Add QuoteCompletionText method to CompletionHelpers class (#25180) (Thanks @ArmaanMcleod!)
    • +
    • Remove CompletionHelpers escape parameter from CompletionRequiresQuotes (#25178) (Thanks @ArmaanMcleod!)
    • +
    • Refactor CompletionHelpers HandleDoubleAndSingleQuote to have less nesting logic (#25179) (Thanks @ArmaanMcleod!)
    • +
    • Make the use of Oxford commas consistent (#25139)(#25140)(Thanks @pressRtowin!)
    • +
    • Move common completion methods to CompletionHelpers class (#25138) (Thanks @ArmaanMcleod!)
    • +
    • Return Array.Empty instead of collection [] (#25137) (Thanks @ArmaanMcleod!)
    • +
    + +
    + +### Tools + +- Check GH token availability for Get-Changelog (#25133) + +### Tests + +- Add XUnit test for `HandleDoubleAndSingleQuote` in CompletionHelpers class (#25181) (Thanks @ArmaanMcleod!) + +### Build and Packaging Improvements + +
    + +
      +
    • Switch to ubuntu-lastest for CI (#25247)
    • +
    • Update outdated package references (#25026)(#25232)
    • +
    • Bump Microsoft.PowerShell.ThreadJob and ThreadJob modules (#25232)
    • +
    • Bump github/codeql-action from 3.27.9 to 3.28.13 (#25218)(#25231)
    • +
    • Update .NET SDK to 10.0.100-preview.2 (#25154)(#25225)
    • +
    • Remove obsolete template from Windows Packaging CI (#25226)
    • +
    • Bump actions/upload-artifact from 4.5.0 to 4.6.2 (#25220)
    • +
    • Bump agrc/reminder-action from 1.0.15 to 1.0.16 (#25222)
    • +
    • Bump actions/checkout from 2 to 4 (#25221)
    • +
    • Add NoWarn NU1605 to System.ServiceModel.* (#25219)
    • +
    • Bump actions/github-script from 6 to 7 (#25217)
    • +
    • Bump ossf/scorecard-action from 2.4.0 to 2.4.1 (#25216)
    • +
    • Bump super-linter/super-linter from 7.2.1 to 7.3.0 (#25215)
    • +
    • Bump agrc/create-reminder-action from 1.1.16 to 1.1.17 (#25214)
    • +
    • Remove dependabot updates that don't work (#25213)
    • +
    • Update GitHub Actions to work in private GitHub repo (#25197)
    • +
    • Cleanup old release pipelines (#25201)
    • +
    • Update package pipeline windows image version (#25191)
    • +
    • Skip additional packages when generating component manifest (#25102)
    • +
    • Only build Linux for packaging changes (#25103)
    • +
    • Remove Az module installs and AzureRM uninstalls in pipeline (#25118)
    • +
    • Add GitHub Actions workflow to verify PR labels (#25145)
    • +
    • Add back-port workflow using dotnet/arcade (#25106)
    • +
    • Make Component Manifest Updater use neutral target in addition to RID target (#25094)
    • +
    • Make sure the vPack pipeline does not produce an empty package (#24988)
    • +
    + +
    + +### Documentation and Help Content + +- Add 7.4.9 changelog (#25169) +- Create changelog for 7.4.8 (#25089) + +[7.6.0-preview.4]: https://github.com/PowerShell/PowerShell/compare/v7.6.0-preview.3...v7.6.0-preview.4 + +## [7.6.0-preview.3] + +### Breaking Changes + +- Remove trailing space from event source name (#24192) (Thanks @MartinGC94!) + +### General Cmdlet Updates and Fixes + +- Add completion single/double quote support for `-Noun` parameter for `Get-Command` (#24977) (Thanks @ArmaanMcleod!) +- Stringify `ErrorRecord` with empty exception message to empty string (#24949) (Thanks @MatejKafka!) +- Add completion single/double quote support for `-PSEdition` parameter for `Get-Module` (#24971) (Thanks @ArmaanMcleod!) +- Error when `New-Item -Force` is passed an invalid directory name (#24936) (Thanks @kborowinski!) +- Allow `Start-Transcript`to use `$Transcript` which is a `PSObject` wrapped string to specify the transcript path (#24963) (Thanks @kborowinski!) +- Add quote handling in `Verb`, `StrictModeVersion`, `Scope` & `PropertyType` Argument Completers with single helper method (#24839) (Thanks @ArmaanMcleod!) +- Improve `Start-Process -Wait` polling efficiency (#24711) (Thanks @jborean93!) +- Convert `InvalidCommandNameCharacters` in `AnalysisCache` to `SearchValues` for more efficient char searching (#24880) (Thanks @ArmaanMcleod!) +- Convert `s_charactersRequiringQuotes` in Completion Completers to `SearchValues` for more efficient char searching (#24879) (Thanks @ArmaanMcleod!) + +### Code Cleanup + +
    + + + +

    We thank the following contributors!

    +

    @xtqqczze, @fMichaleczek, @ArmaanMcleod

    + +
    + +
      +
    • Fix RunspacePool, RunspacePoolInternal and RemoteRunspacePoolInternal IDisposable implementation (#24720) (Thanks @xtqqczze!)
    • +
    • Remove redundant Attribute suffix (#24940) (Thanks @xtqqczze!)
    • +
    • Fix formatting of the XML comment for SteppablePipeline.Clean() (#24941)
    • +
    • Use Environment.ProcessId in SpecialVariables.PID (#24926) (Thanks @fMichaleczek!)
    • +
    • Replace char[] array in CompletionRequiresQuotes with cached SearchValues (#24907) (Thanks @ArmaanMcleod!)
    • +
    • Update IndexOfAny calls with invalid path/filename to SearchValues<char> for more efficient char searching (#24896) (Thanks @ArmaanMcleod!)
    • +
    • Seal internal types in PlatformInvokes (#24826) (Thanks @xtqqczze!)
    • +
    + +
    + +### Tools + +- Update CODEOWNERS (#24989) + +### Build and Packaging Improvements + +
    + + + +

    We thank the following contributors!

    +

    @xtqqczze, @KyZy7

    + +
    + +
      +
    • Update branch for release - Transitive - false - none (#24995)
    • +
    • Add setup dotnet action to the build composite action (#24996)
    • +
    • Give the pipeline runs meaningful names (#24987)
    • +
    • Fix V-Pack download package name (#24866)
    • +
    • Set LangVersion compiler option to 13.0 in Test.Common.props (#24621) (Thanks @xtqqczze!)
    • +
    • Fix release branch filters (#24933)
    • +
    • Fix GitHub Action filter overmatching (#24929)
    • +
    • Add UseDotnet task for installing dotnet (#24905)
    • +
    • Convert powershell/PowerShell-CI-macos to GitHub Actions (#24914)
    • +
    • Convert powershell/PowerShell-CI-linux to GitHub Actions (#24913)
    • +
    • Convert powershell/PowerShell-Windows-CI to GitHub Actions (#24899)
    • +
    • Fix MSIX stage in release pipeline (#24900)
    • +
    • Update .NET SDK (#24906)
    • +
    • Update metadata.json (#24862)
    • +
    • PMC parse state correctly from update command's response (#24850)
    • +
    • Add EV2 support for publishing PowerShell packages to PMC (#24841)
    • +
    • Remove AzDO credscan as it is now in GitHub (#24842)
    • +
    • Add *.props and sort path filters for windows CI (#24822)
    • +
    • Use work load identity service connection to download makeappx tool from storage account (#24817)
    • +
    • Update path filters for Windows CI (#24809)
    • +
    • Update outdated package references (#24758)
    • +
    • Update metadata.json (#24787) (Thanks @KyZy7!)
    • +
    • Add tool package download in publish nuget stage (#24790)
    • +
    • Fix Changelog content grab during GitHub Release (#24788)
    • +
    • Update metadata.json (#24764)
    • +
    • Update Microsoft.PowerShell.PSResourceGet to 1.1.0 (#24767)
    • +
    • Add a parameter that skips verify packages step (#24763)
    • +
    + +
    + +### Documentation and Help Content + +- Add 7.4.7 Changelog (#24844) +- Create changelog for v7.5.0 (#24808) +- Update Changelog for v7.6.0-preview.2 (#24775) + +[7.6.0-preview.3]: https://github.com/PowerShell/PowerShell/compare/v7.6.0-preview.2...v7.6.0-preview.3 + +## [7.6.0-preview.2] - 2025-01-14 + +### General Cmdlet Updates and Fixes + +- Add the `AIShell` module to telemetry collection list (#24747) +- Add helper in `EnumSingleTypeConverter` to get enum names as array (#17785) (Thanks @fflaten!) +- Return correct FileName property for `Get-Item` when listing alternate data streams (#18019) (Thanks @kilasuit!) +- Add `-ExcludeModule` parameter to `Get-Command` (#18955) (Thanks @MartinGC94!) +- Update Named and Statement block type inference to not consider AssignmentStatements and Increment/decrement operators as part of their output (#21137) (Thanks @MartinGC94!) +- Update `DnsNameList` for `X509Certificate2` to use `X509SubjectAlternativeNameExtension.EnumerateDnsNames` Method (#24714) (Thanks @ArmaanMcleod!) +- Add completion of modules by their shortname (#20330) (Thanks @MartinGC94!) +- Fix `Get-ItemProperty` to report non-terminating error for cast exception (#21115) (Thanks @ArmaanMcleod!) +- Add `-PropertyType` argument completer for `New-ItemProperty` (#21117) (Thanks @ArmaanMcleod!) +- Fix a bug in how `Write-Host` handles `XmlNode` object (#24669) (Thanks @brendandburns!) + +### Code Cleanup + +
    + + + +

    We thank the following contributors!

    +

    @xtqqczze

    + +
    + +
      +
    • Seal ClientRemoteSessionDSHandlerImpl (#21218) (Thanks @xtqqczze!)
    • +
    • Seal internal type ClientRemoteSessionDSHandlerImpl (#24705) (Thanks @xtqqczze!)
    • +
    • Seal classes in RemotingProtocol2 (#21164) (Thanks @xtqqczze!)
    • +
    + +
    + +### Tools + +- Added Justin Chung as PowerShell team memeber on releaseTools.psm1 (#24672) + +### Tests + +- Skip CIM ETS member test on older Windows platforms (#24681) + +### Build and Packaging Improvements + +
    + + + +

    Updated SDK to 9.0.101

    + +
    + +
      +
    • Update branch for release - Transitive - false - none (#24754)
    • +
    • Update Microsoft.PowerShell.PSResourceGet to 1.1.0 (#24767)
    • +
    • Add a parameter that skips verify packages step (#24763)
    • +
    • Make the AssemblyVersion not change for servicing releases (#24667)
    • +
    • Fixed release pipeline errors and switched to KS3 (#24751)
    • +
    • Update outdated package references (#24580)
    • +
    • Bump actions/upload-artifact from 4.4.3 to 4.5.0 (#24689)
    • +
    • Update .NET feed with new domain as azureedge is retiring (#24703)
    • +
    • Bump super-linter/super-linter from 7.2.0 to 7.2.1 (#24678)
    • +
    • Bump github/codeql-action from 3.27.7 to 3.27.9 (#24674)
    • +
    • Bump actions/dependency-review-action from 4.4.0 to 4.5.0 (#24607)
    • +
    + +
    + +### Documentation and Help Content + +- Update cmdlets WG members (#24275) (Thanks @kilasuit!) + +[7.6.0-preview.2]: https://github.com/PowerShell/PowerShell/compare/v7.6.0-preview.1...v7.6.0-preview.2 + +## [7.6.0-preview.1] - 2024-12-16 + +### Breaking Changes + +- Treat large Enum values as numbers in `ConvertTo-Json` (#20999) (Thanks @jborean93!) + +### General Cmdlet Updates and Fixes + +- Add proper error for running `Get-PSSession -ComputerName` on Unix (#21009) (Thanks @jborean93!) +- Resolve symbolic link target relative to the symbolic link instead of the working directory (#15235) (#20943) (Thanks @MatejKafka!) +- Fix up buffer management getting network roots (#24600) (Thanks @jborean93!) +- Support `PSObject` wrapped values in `ArgumentToEncodingTransformationAttribute` (#24555) (Thanks @jborean93!) +- Update PSReadLine to 2.3.6 (#24380) +- Add telemetry to track the use of features (#24247) +- Handle global tool specially when prepending `PSHome` to `PATH` (#24228) +- Fix how processor architecture is validated in `Import-Module` (#24265) +- Make features `PSCommandNotFoundSuggestion`, `PSCommandWithArgs`, and `PSModuleAutoLoadSkipOfflineFiles` stable (#24246) +- Write type data to the pipeline instead of collecting it (#24236) (Thanks @MartinGC94!) +- Add support to `Get-Error` to handle BoundParameters (#20640) +- Fix `Get-FormatData` to not cast a type incorrectly (#21157) +- Delay progress bar in `Copy-Item` and `Remove-Item` cmdlets (#24013) (Thanks @TheSpyGod!) +- Add `-Force` parameter to `Resolve-Path` and `Convert-Path` cmdlets to support wildcard hidden files (#20981) (Thanks @ArmaanMcleod!) +- Use host exe to determine `$PSHOME` location when `SMA.dll` location is not found (#24072) +- Fix `Test-ModuleManifest` so it can use a UNC path (#24115) + +### Code Cleanup + +
    + + + +

    We thank the following contributors!

    +

    @eltociear, @JayBazuzi

    + +
    + +
      +
    • Fix typos in ShowModuleControl.xaml.cs (#24248) (Thanks @eltociear!)
    • +
    • Fix a typo in the build doc (#24172) (Thanks @JayBazuzi!)
    • +
    + +
    + +### Tools + +- Fix devcontainer extensions key (#24359) (Thanks @ThomasNieto!) +- Support new backport branch format (#24378) +- Update markdownLink.yml to not run on release branches (#24323) +- Remove old code that downloads msix for win-arm64 (#24175) + +### Tests + +- Fix cleanup in PSResourceGet test (#24339) + +### Build and Packaging Improvements + +
    + + + +

    We thank the following contributors!

    +

    @MartinGC94, @jborean93, @xtqqczze, @alerickson, @iSazonov, @rzippo

    + +
    + +
      +
    • Deploy Box update (#24632)
    • +
    • Remove Regex use (#24235) (Thanks @MartinGC94!)
    • +
    • Improve cim ETS member inference completion (#24235) (Thanks @MartinGC94!)
    • +
    • Emit ProgressRecord in CLIXML minishell output (#21373) (Thanks @jborean93!)
    • +
    • Assign the value returned by the MaybeAdd method
    • (#24652) +
    • Add support for interface static abstract props (#21061) (Thanks @jborean93!)
    • +
    • Change call to optional add in the binder expression (#24451) (Thanks @jborean93!)
    • +
    • Turn off AMSI member invocation on nix release builds (#24451) (Thanks @jborean93!)
    • +
    • Bump github/codeql-action from 3.27.0 to 3.27.6 (#24639)
    • +
    • Update src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs (#24239) (Thanks @jborean93!)
    • +
    • Apply suggestions from code review (#24239) (Thanks @jborean93!)
    • +
    • Add remote runspace check for PushRunspace (#24239) (Thanks @jborean93!)
    • +
    • Set LangVersion compiler option to 13.0 (#24619) (Thanks @xtqqczze!)
    • +
    • Set LangVersion compiler option to 13.0 (#24617) (Thanks @xtqqczze!)
    • +
    • Update metadata.json for PowerShell 7.5 RC1 release (#24589)
    • +
    • Update nuget publish to use Deploy Box (#24596)
    • +
    • Added Deploy Box Product Pathway to GitHub Release and NuGet Release Pipelines (#24583)
    • +
    • Update machine pool for copy blob and upload buildinfo stage (#24587)
    • +
    • Bump .NET 9 and dependencies (#24573)
    • +
    • Bump actions/dependency-review-action from 4.3.4 to 4.4.0 (#24503)
    • +
    • Bump actions/checkout from 4.2.1 to 4.2.2 (#24488)
    • +
    • Bump agrc/reminder-action from 1.0.14 to 1.0.15 (#24384)
    • +
    • Bump actions/upload-artifact from 4.4.0 to 4.4.3 (#24410)
    • +
    • Update branch for release (#24534)
    • +
    • Revert "Update package references (#24414)" (#24532)
    • +
    • Add a way to use only NuGet feed sources (#24528)
    • +
    • Update PSResourceGet to v1.1.0-RC2 (#24512) (Thanks @alerickson!)
    • +
    • Bump .NET to 9.0.100-rc.2.24474.11 (#24509)
    • +
    • Fix seed max value for Container Linux CI (#24510)
    • +
    • Update metadata.json for 7.2.24 and 7.4.6 releases (#24484)
    • +
    • Download package from package build for generating vpack (#24481)
    • +
    • Keep the roff file when gzipping it. (#24450)
    • +
    • Delete the msix blob if it's already there (#24353)
    • +
    • Add PMC mapping for debian 12 (bookworm) (#24413)
    • +
    • Checkin generated manpage (#24423)
    • +
    • Add CodeQL scanning to APIScan build (#24303)
    • +
    • Update package references (#24414)
    • +
    • Update vpack pipeline (#24281)
    • +
    • Bring changes from v7.5.0-preview.5 Release Branch to Master (#24369)
    • +
    • Bump agrc/create-reminder-action from 1.1.15 to 1.1.16 (#24375)
    • +
    • Add BaseUrl to buildinfo json file (#24376)
    • +
    • Update metadata.json (#24352)
    • +
    • Copy to static site instead of making blob public (#24269)
    • +
    • Update Microsoft.PowerShell.PSResourceGet to 1.1.0-preview2 (#24300) (Thanks @alerickson!)
    • +
    • add updated libicu dependency for debian packages (#24301)
    • +
    • add mapping to azurelinux repo (#24290)
    • +
    • Remove the MD5 branch in the strong name signing token calculation (#24288)
    • +
    • Bump .NET 9 to 9.0.100-rc.1.24452.12 (#24273)
    • +
    • Ensure the official build files CodeQL issues (#24278)
    • +
    • Update experimental-feature json files (#24271)
    • +
    • Make some release tests run in a hosted pools (#24270)
    • +
    • Do not build the exe for Global tool shim project (#24263)
    • +
    • Update and add new NuGet package sources for different environments. (#24264)
    • +
    • Bump skitionek/notify-microsoft-teams (#24261)
    • +
    • Create new pipeline for compliance (#24252)
    • +
    • Capture environment better (#24148)
    • +
    • Add specific path for issues in tsaconfig (#24244)
    • +
    • Use Managed Identity for APIScan authentication (#24243)
    • +
    • Add windows signing for pwsh.exe (#24219)
    • +
    • Bump super-linter/super-linter from 7.0.0 to 7.1.0 (#24223)
    • +
    • Update the URLs used in nuget.config files (#24203)
    • +
    • Check Create and Submit in vPack build by default (#24181)
    • +
    • Replace PSVersion source generator with incremental one (#23815) (Thanks @iSazonov!)
    • +
    • Save man files in /usr/share/man instead of /usr/local/share/man (#23855) (Thanks @rzippo!)
    • +
    • Bump super-linter/super-linter from 6.8.0 to 7.0.0 (#24169)
    • +
    + +
    + +### Documentation and Help Content + +- Updated Third Party Notices (#24666) +- Update `HelpInfoUri` for 7.5 (#24610) +- Update changelog for v7.4.6 release (#24496) +- Update to the latest NOTICES file (#24259) +- Update the changelog `preview.md` (#24213) +- Update changelog readme with 7.4 (#24182) (Thanks @ThomasNieto!) +- Fix Markdown linting error (#24204) +- Updated changelog for v7.2.23 (#24196) (Internal 32131) +- Update changelog and `metadata.json` for v7.4.5 release (#24183) +- Bring 7.2 changelogs back to master (#24158) + +[7.6.0-preview.1]: https://github.com/PowerShell/PowerShell/compare/v7.5.0-rc.1...v7.6.0-preview.1 diff --git a/CHANGELOG/preview.md b/CHANGELOG/preview.md index 2663b2e5f0c..8f23d37c9bb 100644 --- a/CHANGELOG/preview.md +++ b/CHANGELOG/preview.md @@ -1,58 +1,61 @@ # Preview Changelog -## [7.6.0-preview.4] +## [7.7.0-preview.1] ### Breaking Changes -- Fix `WildcardPattern.Escape` to escape lone backticks correctly (#25211) (Thanks @ArmaanMcleod!) -- Convert `-ChildPath` parameter to `string[]` for `Join-Path` cmdlet (#24677) (Thanks @ArmaanMcleod!) - -PowerShell 7.6-preview.4 includes the following updated modules: - -- **Microsoft.PowerShell.ThreadJob** v2.2.0 -- **ThreadJob** v2.1.0 -The **ThreadJob** module was renamed to **Microsoft.PowerShell.ThreadJob**. There is no difference -in the functionality of the module. To ensure backward compatibility for scripts that use the old -name, the **ThreadJob** v2.1.0 module is a proxy module that points to the -**Microsoft.PowerShell.ThreadJob** v2.2.0. +- Add `ValidateNotNullOrEmpty` attribute to the `-Property` of `Format-Table/List/Custom` (#26552) +- Fix to use accurate message for validating a string argument is not null and not an empty string (#26668) +- Correct handling of explicit `-[Operator]:$false` parameter values in `Where-Object` (#26485) (Thanks @yotsuda!) ### Engine Updates and Fixes -- Add `PipelineStopToken` to `Cmdlet` which will be signaled when the pipeline is stopping (#24620) (Thanks @jborean93!) -- Fallback to AppLocker after `WldpCanExecuteFile` (#24912) -- Move .NET method invocation logging to after the needed type conversion is done for method arguments (#25022) -- Fix share completion with provider and spaces (#19440) (Thanks @MartinGC94!) +- Update `MaxVisitCount` and `MaxHashtableKeyCount` if `VisitorSafeValueContext` indicates `SkipLimitCheck` is true +(#27308) +- Enable usage in AppContainers (#27305) +- Delay update notification for one week to ensure all packages become available (#27095) +- Fix up default value for parameters with the `in` modifier (#26785) (Thanks @jborean93!) +- Fix `WSManInstance` COM interface with `ResourceURI` (#26692) (Thanks @jborean93!) +- Refactor the module path construction code to make it more robust and easier to maintain (#26565) +- Fix checks for local user config file paths (#26269) ### General Cmdlet Updates and Fixes -- Exclude `-OutVariable` assignments within the same `CommandAst` when inferring variables (#25224) (Thanks @MartinGC94!) -- Fix infinite loop in variable type inference (#25206) (Thanks @MartinGC94!) -- Update `Microsoft.PowerShell.PSResourceGet` version in `PSGalleryModules.csproj` (#25135) -- Add tooltips for hashtable key completions (#17864) (Thanks @MartinGC94!) -- Fix type inference of parameters in classic functions (#25172) (Thanks @MartinGC94!) -- Improve assignment type inference (#21143) (Thanks @MartinGC94!) -- Fix `TypeName.GetReflectionType()` to work when the `TypeName` instance represents a generic type definition within a `GenericTypeName` (#24985) -- Remove the old fuzzy suggestion and fix the local script filename suggestion (#25177) -- Improve variable type inference (#19830) (Thanks @MartinGC94!) -- Fix parameter completion when script requirements fail (#17687) (Thanks @MartinGC94!) -- Improve the completion for attribute arguments (#25129) (Thanks @MartinGC94!) -- Fix completion that relies on pseudobinding in script blocks (#25122) (Thanks @MartinGC94!) -- Don't complete duplicate command names (#21113) (Thanks @MartinGC94!) -- Make `SystemPolicy` public APIs visible but non-op on Unix platforms so that they can be included in `PowerShellStandard.Library` (#25051) -- Set standard handles explicitly when starting a process with `-NoNewWindow` (#25061) -- Fix tooltip for variable expansion and include desc (#25112) (Thanks @jborean93!) -- Add type inference for functions without OutputType attribute and anonymous functions (#21127) (Thanks @MartinGC94!) -- Add completion for variables assigned by command redirection (#25104) (Thanks @MartinGC94!) -- Handle type inference for redirected commands (#21131) (Thanks @MartinGC94!) -- Allow empty prefix string in `Import-Module -Prefix` to override default prefix in manifest (#20409) (Thanks @MartinGC94!) -- Update variable/property assignment completion so it can fallback to type inference (#21134) (Thanks @MartinGC94!) -- Use `Get-Help` approach to find `about_*.help.txt` files with correct locale for completions (#24194) (Thanks @MartinGC94!) -- Use script filepath when completing relative paths for using statements (#20017) (Thanks @MartinGC94!) -- Fix completion of variables assigned inside Do loops (#25076) (Thanks @MartinGC94!) -- Fix completion of provider paths when a path returns itself instead of its children (#24755) (Thanks @MartinGC94!) -- Enable completion of scoped variables without specifying scope (#20340) (Thanks @MartinGC94!) -- Fix issue with incomplete results when completing paths with wildcards in non-filesystem providers (#24757) (Thanks @MartinGC94!) -- Allow DSC parsing through OS architecture translation layers (#24852) (Thanks @bdeb1337!) +- Add verbose message to `Get-Service` when properties cannot be returned (#27109) (Thanks @reabr!) +- Fix `Remove-Item` confirmation message to use provider path instead (#27123) (Thanks @scuzqy!) +- PSStyle: validate background index against `BackgroundColorMap` (#27106) (Thanks @cuiweixie!) +- Update PowerShell Profile DSC resource manifests to allow null for content (#26929) +- Add `SubjectAlternativeName` property to the `Signature` object returned from `Get-AuthenticodeSignature` (#26252) +- Mark `-NoTypeInformation` as obsolete no-op and evaluate `-IncludeTypeInformation` on by value on Csv cmdlets (#26719) (Thanks @yotsuda!) +- Support `TargetObject` position in `ParserErrors` (#26649) (Thanks @jborean93!) +- Fix the CLR internal error and null ref exception when running `show-command` with PowerShell API (#26669) +- Fix `Test-Json` false positive errors when using `oneOf` or `anyOf` in schema (#26618) (Thanks @yotsuda!) +- Add `ToRegex` method to `WildcardPattern` class (#26515) (Thanks @yotsuda!) +- Add `-ExcludeProperty` parameter to `Format-*` cmdlets (#26514) (Thanks @yotsuda!) +- Fix NOTES section formatting in comment-based help (#26512) (Thanks @yotsuda!) +- Disable AMSI content logging in release (#26235) (Thanks @xtqqczze!) +- Add tab completion for `$PSBoundParameters.Keys` switch cases and access patterns (#26483) (Thanks @yotsuda!) +- Fix formatting to properly handle the `Reset` VT sequences that appear in the middle of a string (#26424) +- Add `-Extension` parameter to `Join-Path` cmdlet (#26482) (Thanks @yotsuda!) +- Make `Export-Csv` `-Append` and `-NoHeader` mutually exclusive (#26472) (Thanks @yotsuda!) +- Respect `-Qualifier/-NoQualifier/-Leaf/-IsAbsolute:$false` in `Split-Path` (#26474) (Thanks @yotsuda!) +- Respect `-UseWindowsPowerShell:$false` in `New-PSSession` (#26469) (Thanks @yotsuda!) +- Respect `-Repeat/-MtuSize/-Traceroute:$false` in `Test-Connection` (#26479) (Thanks @yotsuda!) +- Fix `Invoke-RestMethod` to support read-only files in multipart form data (#26454) (Thanks @yotsuda!) +- Respect `-ListAvailable:$false` in `Get-TimeZone` (#26463) (Thanks @yotsuda!) +- Respect `-Shuffle:$false` in `Get-SecureRandom` (#26460) (Thanks @yotsuda!) +- Respect `-Shuffle:$false` in `Get-Random` (#26457) (Thanks @yotsuda!) +- DSC v3 resource for Powershell Profile (#26157) +- Make the experimental feature `PSFeedbackProvider` stable (#26343) +- Make some experimental features stable (#26348) +- Add `PSApplicationOutputEncoding` variable (#21219) (Thanks @jborean93!) +- Dynamically evaluate width of `LastWriteTime` for formatting output on Unix (#24624) (Thanks @MathiasMagnus!) +- Handle null reference exception in CsvCommands.cs: `ConvertPSObjectToCSV` (#26144) (Thanks @mikkas456!) +- Improve `ValidateLength` error message consistency and refactor validation tests (#25806) (Thanks @jorgeasaurus!) +- Correct handling of explicit `-Since:$false` parameter value in `Get-Uptime` (#26141) (Thanks @logiclrd!) +- Add property and event for debug attach (#25788) (Thanks @jborean93!) +- Fix memory leak in `GetFileShares` (#25896) (Thanks @xtqqczze!) +- Correct handling of explicit `-Empty:$false` parameter value in `New-Guid` (#26140) (Thanks @logiclrd!) ### Code Cleanup @@ -61,115 +64,81 @@ name, the **ThreadJob** v2.1.0 module is a proxy module that points to the

    We thank the following contributors!

    -

    @ArmaanMcleod, @pressRtowin

    +

    @xtqqczze, @yotsuda, @ThioJoe, @rwp0, @amritanand-py

      -
    • Refactor and add comments to CompletionRequiresQuotes to clarify implementation (#25223) (Thanks @ArmaanMcleod!)
    • -
    • Add QuoteCompletionText method to CompletionHelpers class (#25180) (Thanks @ArmaanMcleod!)
    • -
    • Remove CompletionHelpers escape parameter from CompletionRequiresQuotes (#25178) (Thanks @ArmaanMcleod!)
    • -
    • Refactor CompletionHelpers HandleDoubleAndSingleQuote to have less nesting logic (#25179) (Thanks @ArmaanMcleod!)
    • -
    • Make the use of Oxford commas consistent (#25139)(#25140)(Thanks @pressRtowin!)
    • -
    • Move common completion methods to CompletionHelpers class (#25138) (Thanks @ArmaanMcleod!)
    • -
    • Return Array.Empty instead of collection [] (#25137) (Thanks @ArmaanMcleod!)
    • +
    • Fix IDisposable implementation in sealed classes (#26215) (Thanks @xtqqczze!)
    • +
    • Enable CA1852: Seal internal types (#25890) (Thanks @xtqqczze!)
    • +
    • Remove obsolete CA2006 rule suppression (#25939) (Thanks @xtqqczze!)
    • +
    • Use consistent indentation in the file HelpersCommon.psm1 (#26608)
    • +
    • Centralize ExcludeProperty filter application in ViewGenerator base class (#26574) (Thanks @yotsuda!)
    • +
    • Refactor IsComputerNameValid character validation (#26274) (Thanks @xtqqczze!)
    • +
    • Remove obsolete test/docker/networktest directory (#26388)
    • +
    • Avoid regex for exact word matching in DscClassCache (#26306) (Thanks @xtqqczze!)
    • +
    • Enable analyzers: Use char overload (#26301) (Thanks @xtqqczze!)
    • +
    • Enable CA1200: Avoid using cref tags with a prefix (#26298) (Thanks @xtqqczze!)
    • +
    • Remove unused timeout variable from RemoteHyperVTests class (#26297) (Thanks @xtqqczze!)
    • +
    • Enable CA2022: Avoid inexact read with Stream.Read (#25814) (Thanks @xtqqczze!)
    • +
    • Fix a few simple typos in comments and string outputs (#25805) (Thanks @ThioJoe!)
    • +
    • Remove unused Azure Devops windows CI workflows (#26245)
    • +
    • Fix CA1837: Use Environment.ProcessId (#26242) (Thanks @xtqqczze!)
    • +
    • Enable IDE0080: RemoveConfusingSuppressionForIsExpression (#26206) (Thanks @xtqqczze!)
    • +
    • Remove redundant CharSet from StructLayout attributes. Part 1 (#26216) (Thanks @xtqqczze!)
    • +
    • Fix IDE0083: UseNotPattern (#26213) (Thanks @xtqqczze!)
    • +
    • Fix IDE0049 for string in System.Management.Automation (#25921) (Thanks @xtqqczze!)
    • +
    • Fix IDE0049 for object in System.Management.Automation. Part 1 (#25923) (Thanks @xtqqczze!)
    • +
    • Replace stackallocs with collection expressions (#25803) (Thanks @xtqqczze!)
    • +
    • Capitalize Windows in PSNativeWindowsTildeExpansion experimental feature description (#25266) (Thanks @rwp0!)
    • +
    • Fix SA1028: Code should not contain trailing whitespace. Part 1. (#26203) (Thanks @xtqqczze!)
    • +
    • Fix IDE0083: UseNotPattern (#26209) (Thanks @xtqqczze!)
    • +
    • Fix CA1852: Seal internal types. Part 1 (#26205) (Thanks @xtqqczze!)
    • +
    • Enable IDE0019: InlineAsTypeCheck (#25920) (Thanks @xtqqczze!)
    • +
    • Fix mismatched indentation in .config/suppress.json (#26192) (Thanks @xtqqczze!)
    • +
    • Replace custom method with File.ReadAllText() in ScriptAnalysis.cs (#26060) (Thanks @amritanand-py!)
    • +
    • Avoid possible multiple enumerations in ImportModuleCommand.IsPs1xmlFileHelper_IsPresentInEntries (#26104) (Thanks @xtqqczze!)
    • +
    • Enable SA1206: Declaration keywords should follow order (#24973) (Thanks @xtqqczze!)
    • +
    • Disable IDE0049: PreferBuiltInOrFrameworkType (#26094) (Thanks @xtqqczze!)
    • +
    • Enable CA1853: Unnecessary call to Dictionary.ContainsKey(key) (#26106) (Thanks @xtqqczze!)
    • +
    • Enable CA1860: Avoid using Enumerable.Any() extension method (#26109) (Thanks @xtqqczze!)
    • +
    • Enable CA1858: Use StartsWith instead of IndexOf (#26107) (Thanks @xtqqczze!)
    • +
    • Add CodeQL suppressions for NativeCommandProcessor (#26729)
    ### Tools -- Check GH token availability for Get-Changelog (#25133) +- Add GitOps policy to auto-label backport candidates when CL-BuildPackaging is added (#26881) +- Add Pester CI Analysis Skill (#26806) +- Delete unused winget release script (#26683) +- Improve error message from `Start-NativeExecution` (#26500) (Thanks @logiclrd!) +- Add default CODEOWNERS entry for maintainers (#26660) +- Add Attack Surface Analyzer Script (#26379) +- Add merge conflict marker detection to linux-ci workflow and refactor existing actions to use reusable get-changed-files action (#26350) +- Add reusable get-changed-files action and refactor existing actions (#26355) +- Refactor analyze job to reusable workflow and enable on Windows CI (#26322) +- Create github copilot setup workflow (#26285) +- Update dependabot.yml to monitor release/* branches (#26251) ### Tests -- Add XUnit test for `HandleDoubleAndSingleQuote` in CompletionHelpers class (#25181) (Thanks @ArmaanMcleod!) - -### Build and Packaging Improvements - -
    - -
      -
    • Switch to ubuntu-lastest for CI (#25247)
    • -
    • Update outdated package references (#25026)(#25232)
    • -
    • Bump Microsoft.PowerShell.ThreadJob and ThreadJob modules (#25232)
    • -
    • Bump github/codeql-action from 3.27.9 to 3.28.13 (#25218)(#25231)
    • -
    • Update .NET SDK to 10.0.100-preview.2 (#25154)(#25225)
    • -
    • Remove obsolete template from Windows Packaging CI (#25226)
    • -
    • Bump actions/upload-artifact from 4.5.0 to 4.6.2 (#25220)
    • -
    • Bump agrc/reminder-action from 1.0.15 to 1.0.16 (#25222)
    • -
    • Bump actions/checkout from 2 to 4 (#25221)
    • -
    • Add NoWarn NU1605 to System.ServiceModel.* (#25219)
    • -
    • Bump actions/github-script from 6 to 7 (#25217)
    • -
    • Bump ossf/scorecard-action from 2.4.0 to 2.4.1 (#25216)
    • -
    • Bump super-linter/super-linter from 7.2.1 to 7.3.0 (#25215)
    • -
    • Bump agrc/create-reminder-action from 1.1.16 to 1.1.17 (#25214)
    • -
    • Remove dependabot updates that don't work (#25213)
    • -
    • Update GitHub Actions to work in private GitHub repo (#25197)
    • -
    • Cleanup old release pipelines (#25201)
    • -
    • Update package pipeline windows image version (#25191)
    • -
    • Skip additional packages when generating component manifest (#25102)
    • -
    • Only build Linux for packaging changes (#25103)
    • -
    • Remove Az module installs and AzureRM uninstalls in pipeline (#25118)
    • -
    • Add GitHub Actions workflow to verify PR labels (#25145)
    • -
    • Add back-port workflow using dotnet/arcade (#25106)
    • -
    • Make Component Manifest Updater use neutral target in addition to RID target (#25094)
    • -
    • Make sure the vPack pipeline does not produce an empty package (#24988)
    • -
    - -
    - -### Documentation and Help Content - -- Add 7.4.9 changelog (#25169) -- Create changelog for 7.4.8 (#25089) - -[7.6.0-preview.4]: https://github.com/PowerShell/PowerShell/compare/v7.6.0-preview.3...v7.6.0-preview.4 - -## [7.6.0-preview.3] - -### Breaking Changes - -- Remove trailing space from event source name (#24192) (Thanks @MartinGC94!) - -### General Cmdlet Updates and Fixes - -- Add completion single/double quote support for `-Noun` parameter for `Get-Command` (#24977) (Thanks @ArmaanMcleod!) -- Stringify `ErrorRecord` with empty exception message to empty string (#24949) (Thanks @MatejKafka!) -- Add completion single/double quote support for `-PSEdition` parameter for `Get-Module` (#24971) (Thanks @ArmaanMcleod!) -- Error when `New-Item -Force` is passed an invalid directory name (#24936) (Thanks @kborowinski!) -- Allow `Start-Transcript`to use `$Transcript` which is a `PSObject` wrapped string to specify the transcript path (#24963) (Thanks @kborowinski!) -- Add quote handling in `Verb`, `StrictModeVersion`, `Scope` & `PropertyType` Argument Completers with single helper method (#24839) (Thanks @ArmaanMcleod!) -- Improve `Start-Process -Wait` polling efficiency (#24711) (Thanks @jborean93!) -- Convert `InvalidCommandNameCharacters` in `AnalysisCache` to `SearchValues` for more efficient char searching (#24880) (Thanks @ArmaanMcleod!) -- Convert `s_charactersRequiringQuotes` in Completion Completers to `SearchValues` for more efficient char searching (#24879) (Thanks @ArmaanMcleod!) - -### Code Cleanup - -
    - - - -

    We thank the following contributors!

    -

    @xtqqczze, @fMichaleczek, @ArmaanMcleod

    - -
    - -
      -
    • Fix RunspacePool, RunspacePoolInternal and RemoteRunspacePoolInternal IDisposable implementation (#24720) (Thanks @xtqqczze!)
    • -
    • Remove redundant Attribute suffix (#24940) (Thanks @xtqqczze!)
    • -
    • Fix formatting of the XML comment for SteppablePipeline.Clean() (#24941)
    • -
    • Use Environment.ProcessId in SpecialVariables.PID (#24926) (Thanks @fMichaleczek!)
    • -
    • Replace char[] array in CompletionRequiresQuotes with cached SearchValues (#24907) (Thanks @ArmaanMcleod!)
    • -
    • Update IndexOfAny calls with invalid path/filename to SearchValues<char> for more efficient char searching (#24896) (Thanks @ArmaanMcleod!)
    • -
    • Seal internal types in PlatformInvokes (#24826) (Thanks @xtqqczze!)
    • -
    - -
    - -### Tools - -- Update CODEOWNERS (#24989) +- Fix the `PSNativeCommandArgumentPassing` test (#27057) +- Fix `Import-Module.Tests.ps1` to handle Arm32 platform (#26862) +- Add comprehensive PowerShell class tests for `ConvertTo-Json` (#26769) (Thanks @yotsuda!) +- Add comprehensive `PSCustomObject` tests for `ConvertTo-Json` (#26743) (Thanks @yotsuda!) +- Add GitHub Actions annotations for Pester test failures (#26789) +- Add comprehensive depth and multilevel composition tests for `ConvertTo-Json` (#26744) (Thanks @yotsuda!) +- Add comprehensive array and dictionary tests for `ConvertTo-Json` (#26742) (Thanks @yotsuda!) +- Add comprehensive scalar type tests for `ConvertTo-Json` (#26736) (Thanks @yotsuda!) +- Fix the fuzzy test (#26402) +- Add Fuzz Tests (#26384) +- Fix merge conflict checker for empty file lists and filter *.cs files (#26365) +- Fix linux_packaging job being skipped when only packaging files change (#26315) +- Use `[initialsessionstate]` type accelerator (#25912) (Thanks @xtqqczze!) +- Add markdown link verification for PRs (#26219) +- Check for `GetWindowPlacement` success (#26122) (Thanks @xtqqczze!) ### Build and Packaging Improvements @@ -178,270 +147,106 @@ name, the **ThreadJob** v2.1.0 module is a proxy module that points to the

    We thank the following contributors!

    -

    @xtqqczze, @KyZy7

    +

    @powercode, @kasperk81, @xtqqczze

      -
    • Update branch for release - Transitive - false - none (#24995)
    • -
    • Add setup dotnet action to the build composite action (#24996)
    • -
    • Give the pipeline runs meaningful names (#24987)
    • -
    • Fix V-Pack download package name (#24866)
    • -
    • Set LangVersion compiler option to 13.0 in Test.Common.props (#24621) (Thanks @xtqqczze!)
    • -
    • Fix release branch filters (#24933)
    • -
    • Fix GitHub Action filter overmatching (#24929)
    • -
    • Add UseDotnet task for installing dotnet (#24905)
    • -
    • Convert powershell/PowerShell-CI-macos to GitHub Actions (#24914)
    • -
    • Convert powershell/PowerShell-CI-linux to GitHub Actions (#24913)
    • -
    • Convert powershell/PowerShell-Windows-CI to GitHub Actions (#24899)
    • -
    • Fix MSIX stage in release pipeline (#24900)
    • -
    • Update .NET SDK (#24906)
    • -
    • Update metadata.json (#24862)
    • -
    • PMC parse state correctly from update command's response (#24850)
    • -
    • Add EV2 support for publishing PowerShell packages to PMC (#24841)
    • -
    • Remove AzDO credscan as it is now in GitHub (#24842)
    • -
    • Add *.props and sort path filters for windows CI (#24822)
    • -
    • Use work load identity service connection to download makeappx tool from storage account (#24817)
    • -
    • Update path filters for Windows CI (#24809)
    • -
    • Update outdated package references (#24758)
    • -
    • Update metadata.json (#24787) (Thanks @KyZy7!)
    • -
    • Add tool package download in publish nuget stage (#24790)
    • -
    • Fix Changelog content grab during GitHub Release (#24788)
    • -
    • Update metadata.json (#24764)
    • -
    • Update Microsoft.PowerShell.PSResourceGet to 1.1.0 (#24767)
    • -
    • Add a parameter that skips verify packages step (#24763)
    • +
    • Update branch for release (#27291)
    • +
    • Remove package verification from the notice pipeline (#27289)
    • +
    • Remove MSI from publishing pipeline (#27213)
    • +
    • Externalize findMissingNotices target framework selection with ordered Windows fallback (#27269)
    • +
    • Fix the package pipeline by adding in PDP-Media directory (#27254)
    • +
    • Bump actions/checkout from 4 to 6.0.2 (#27206)
    • +
    • Build, package, and create VPack for the PowerShell-LTS store package within the same msixbundle-vpack pipeline (#150) (#27209)
    • +
    • Pin ready-to-merge.yml reusable workflow to commit SHA (#27204)
    • +
    • Change the display name of PowerShell-LTS MSIX package to "PowerShell LTS" (#27203)
    • +
    • [StepSecurity] ci: Harden GitHub Actions (#27201)
    • +
    • [StepSecurity] ci: Harden GitHub Actions (#27202)
    • +
    • Redo windows image fix to use latest image (#27198)
    • +
    • Separate Store Package Creation, Skip Polling for Store Publish, Clean up PDP-Media (#27024)
    • +
    • Revert "Fetch latest ICU release version dynamically" (#27127)
    • +
    • Update package references and move to .NET SDK 11.0-preview.2 (#27117)
    • +
    • Add comment-based help documentation to build.psm1 functions (#27122) (Thanks @powercode!)
    • +
    • Bump github/codeql-action from 3.30.3 to 4.35.1 (#27120)
    • +
    • Select New MSIX Package Name (#27096)
    • +
    • Separate Official and NonOfficial templates for ADO pipelines (#26897)
    • +
    • Update the PhoneProductId to be the official LTS id used by Store (#27077)
    • +
    • release-upload-buildinfo: replace version-comparison channel gating with metadata flags (#27074)
    • +
    • Update build to create two msix's and msixbundles for LTS and Stable (#27056)
    • +
    • Update metadata.json for the v7.6.0 release (#27054)
    • +
    • Move _GetDependencies MSBuild target from dynamic generation in build.psm1 into Microsoft.PowerShell.SDK.csproj (#27052)
    • +
    • Fix PMC repo URL for RHEL10 (#27059)
    • +
    • Create Linux LTS deb/rpm packages for LTS releases (#27049)
    • +
    • Create LTS pkg and non-LTS pkg for macOS for LTS releases (#27039)
    • +
    • Fix the container image for vPack, MSIX vPack and Package pipelines (#27015)
    • +
    • Update Microsoft.PowerShell.PSResourceGet version to 1.2.0 (#27003)
    • +
    • Fix ConvertFrom-ClearlyDefinedCoordinates to handle API object coordinates (#26893)
    • +
    • Bump actions/upload-artifact from 4 to 7 (#26914)
    • +
    • Bump actions/dependency-review-action from 4.7.3 to 4.9.0 (#26938)
    • + +
    • Hardcode Official templates (#26928)
    • +
    • Add PMC packages for debian13 and rhel10 (#26912)
    • +
    • Split TPN manifest and Component Governance manifest (#26891)
    • +
    • Add version in description and pass store task on failure (#26885)
    • +
    • Correct the package name for .deb and .rpm packages (#26877)
    • +
    • Fix a preview detection test for the packaging script (#26882)
    • +
    • Exclude .exe packages from publishing to GitHub (#26859)
    • +
    • Update metadata.json for v7.6.0-rc.1 (#26856)
    • +
    • Fetch latest ICU release version dynamically (#26827) (Thanks @kasperk81!)
    • +
    • Update LangVersion to preview (#26214) (Thanks @xtqqczze!)
    • +
    • Update to .NET 11 SDK and update dependencies (#26783)
    • +
    • Update outdated package references (#26771)
    • +
    • Create es-metadata (#26759)
    • +
    • Add policy to restrict the Approved-LowRisk label (#26728)
    • +
    • Move PowerShell build to depend on .NET SDK 10.0.102 (#26697)
    • +
    • Update metadata.json to update the Latest attribute with a better name (#26380)
    • +
    • Update outdated package references (#26656)
    • +
    • Bring release changes from the v7.6.0-preview.6 release branch (#26627)
    • +
    • Update build to use .NET SDK 10.0.100 (#26448)
    • +
    • Update the macos package name for preview releases to match the previous pattern (#26429)
    • +
    • Fix condition syntax for StoreBroker package tasks in MSIX pipeline (#26427)
    • +
    • Fix template path for rebuild branch check in package.yml (#26425)
    • +
    • Update the WCF packages to the latest version that is compatible with v4.10.3 (#26406)
    • +
    • Add rebuild branch support with conditional MSIX signing (#26415)
    • +
    • Optimize/split windows package signing (#26403)
    • +
    • Improve ADO package build and validation across platforms (#26398)
    • +
    • Update outdated test package references (#26368)
    • +
    • Delete this way of collecting feedback (#26364)
    • +
    • Update the Microsoft.PowerShell.Native package version (#26347)
    • +
    • Add log grouping to build.psm1 for collapsible GitHub Actions logs (#26326)
    • +
    • Bump actions/setup-dotnet from 4 to 5 (#26327)
    • +
    • Update SDK to 10.0.100-rc.2.25502.107 (#26305)
    • +
    • Replace fpm with dpkg-deb for DEB package generation (#26281)
    • +
    • Replace fpm with native macOS packaging tools (pkgbuild/productbuild) (#26268)
    • +
    • Separate Store Automation Service Endpoints, Resolve AppID (#26210)
    • +
    • Update concurrency groups to prevent merge runs and pull request runs from canceling each other (#26257)
    • +
    • Update release tags to version 7.5.4 and 7.4.13 (#26258)
    • +
    • Update outdated package references (#26148)
    • +
    • Refactor: Centralize xUnit tests into reusable workflow and remove legacy verification (#26243)
    • +
    • Convert Azure DevOps Linux Packaging pipeline to GitHub Actions workflow (#26225)
    • +
    • Update vPack name (#26090)
    • +
    • Update metadata.json for v7.6.0-preview.5 release (#26158)
    • +
    • Bump ossf/scorecard-action from 2.4.2 to 2.4.3 (#26128)
    ### Documentation and Help Content -- Add 7.4.7 Changelog (#24844) -- Create changelog for v7.5.0 (#24808) -- Update Changelog for v7.6.0-preview.2 (#24775) - -[7.6.0-preview.3]: https://github.com/PowerShell/PowerShell/compare/v7.6.0-preview.2...v7.6.0-preview.3 - -## [7.6.0-preview.2] - 2025-01-14 - -### General Cmdlet Updates and Fixes - -- Add the `AIShell` module to telemetry collection list (#24747) -- Add helper in `EnumSingleTypeConverter` to get enum names as array (#17785) (Thanks @fflaten!) -- Return correct FileName property for `Get-Item` when listing alternate data streams (#18019) (Thanks @kilasuit!) -- Add `-ExcludeModule` parameter to `Get-Command` (#18955) (Thanks @MartinGC94!) -- Update Named and Statement block type inference to not consider AssignmentStatements and Increment/decrement operators as part of their output (#21137) (Thanks @MartinGC94!) -- Update `DnsNameList` for `X509Certificate2` to use `X509SubjectAlternativeNameExtension.EnumerateDnsNames` Method (#24714) (Thanks @ArmaanMcleod!) -- Add completion of modules by their shortname (#20330) (Thanks @MartinGC94!) -- Fix `Get-ItemProperty` to report non-terminating error for cast exception (#21115) (Thanks @ArmaanMcleod!) -- Add `-PropertyType` argument completer for `New-ItemProperty` (#21117) (Thanks @ArmaanMcleod!) -- Fix a bug in how `Write-Host` handles `XmlNode` object (#24669) (Thanks @brendandburns!) - -### Code Cleanup - -
    - - - -

    We thank the following contributors!

    -

    @xtqqczze

    - -
    - -
      -
    • Seal ClientRemoteSessionDSHandlerImpl (#21218) (Thanks @xtqqczze!)
    • -
    • Seal internal type ClientRemoteSessionDSHandlerImpl (#24705) (Thanks @xtqqczze!)
    • -
    • Seal classes in RemotingProtocol2 (#21164) (Thanks @xtqqczze!)
    • -
    - -
    - -### Tools - -- Added Justin Chung as Powershell team memeber on releaseTools.psm1 (#24672) - -### Tests - -- Skip CIM ETS member test on older Windows platforms (#24681) - -### Build and Packaging Improvements - -
    - - - -

    Updated SDK to 9.0.101

    - -
    - -
      -
    • Update branch for release - Transitive - false - none (#24754)
    • -
    • Update Microsoft.PowerShell.PSResourceGet to 1.1.0 (#24767)
    • -
    • Add a parameter that skips verify packages step (#24763)
    • -
    • Make the AssemblyVersion not change for servicing releases (#24667)
    • -
    • Fixed release pipeline errors and switched to KS3 (#24751)
    • -
    • Update outdated package references (#24580)
    • -
    • Bump actions/upload-artifact from 4.4.3 to 4.5.0 (#24689)
    • -
    • Update .NET feed with new domain as azureedge is retiring (#24703)
    • -
    • Bump super-linter/super-linter from 7.2.0 to 7.2.1 (#24678)
    • -
    • Bump github/codeql-action from 3.27.7 to 3.27.9 (#24674)
    • -
    • Bump actions/dependency-review-action from 4.4.0 to 4.5.0 (#24607)
    • -
    - -
    - -### Documentation and Help Content - -- Update cmdlets WG members (#24275) (Thanks @kilasuit!) - -[7.6.0-preview.2]: https://github.com/PowerShell/PowerShell/compare/v7.6.0-preview.1...v7.6.0-preview.2 - -## [7.6.0-preview.1] - 2024-12-16 - -### Breaking Changes - -- Treat large Enum values as numbers in `ConvertTo-Json` (#20999) (Thanks @jborean93!) - -### General Cmdlet Updates and Fixes - -- Add proper error for running `Get-PSSession -ComputerName` on Unix (#21009) (Thanks @jborean93!) -- Resolve symbolic link target relative to the symbolic link instead of the working directory (#15235) (#20943) (Thanks @MatejKafka!) -- Fix up buffer management getting network roots (#24600) (Thanks @jborean93!) -- Support `PSObject` wrapped values in `ArgumentToEncodingTransformationAttribute` (#24555) (Thanks @jborean93!) -- Update PSReadLine to 2.3.6 (#24380) -- Add telemetry to track the use of features (#24247) -- Handle global tool specially when prepending `PSHome` to `PATH` (#24228) -- Fix how processor architecture is validated in `Import-Module` (#24265) -- Make features `PSCommandNotFoundSuggestion`, `PSCommandWithArgs`, and `PSModuleAutoLoadSkipOfflineFiles` stable (#24246) -- Write type data to the pipeline instead of collecting it (#24236) (Thanks @MartinGC94!) -- Add support to `Get-Error` to handle BoundParameters (#20640) -- Fix `Get-FormatData` to not cast a type incorrectly (#21157) -- Delay progress bar in `Copy-Item` and `Remove-Item` cmdlets (#24013) (Thanks @TheSpyGod!) -- Add `-Force` parameter to `Resolve-Path` and `Convert-Path` cmdlets to support wildcard hidden files (#20981) (Thanks @ArmaanMcleod!) -- Use host exe to determine `$PSHOME` location when `SMA.dll` location is not found (#24072) -- Fix `Test-ModuleManifest` so it can use a UNC path (#24115) - -### Code Cleanup - -
    - - - -

    We thank the following contributors!

    -

    @eltociear, @JayBazuzi

    - -
    - -
      -
    • Fix typos in ShowModuleControl.xaml.cs (#24248) (Thanks @eltociear!)
    • -
    • Fix a typo in the build doc (#24172) (Thanks @JayBazuzi!)
    • -
    - -
    - -### Tools - -- Fix devcontainer extensions key (#24359) (Thanks @ThomasNieto!) -- Support new backport branch format (#24378) -- Update markdownLink.yml to not run on release branches (#24323) -- Remove old code that downloads msix for win-arm64 (#24175) - -### Tests - -- Fix cleanup in PSResourceGet test (#24339) - -### Build and Packaging Improvements - -
    - - - -

    We thank the following contributors!

    -

    @MartinGC94, @jborean93, @xtqqczze, @alerickson, @iSazonov, @rzippo

    - -
    - -
      -
    • Deploy Box update (#24632)
    • -
    • Remove Regex use (#24235) (Thanks @MartinGC94!)
    • -
    • Improve cim ETS member inference completion (#24235) (Thanks @MartinGC94!)
    • -
    • Emit ProgressRecord in CLIXML minishell output (#21373) (Thanks @jborean93!)
    • -
    • Assign the value returned by the MaybeAdd method
    • (#24652) -
    • Add support for interface static abstract props (#21061) (Thanks @jborean93!)
    • -
    • Change call to optional add in the binder expression (#24451) (Thanks @jborean93!)
    • -
    • Turn off AMSI member invocation on nix release builds (#24451) (Thanks @jborean93!)
    • -
    • Bump github/codeql-action from 3.27.0 to 3.27.6 (#24639)
    • -
    • Update src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs (#24239) (Thanks @jborean93!)
    • -
    • Apply suggestions from code review (#24239) (Thanks @jborean93!)
    • -
    • Add remote runspace check for PushRunspace (#24239) (Thanks @jborean93!)
    • -
    • Set LangVersion compiler option to 13.0 (#24619) (Thanks @xtqqczze!)
    • -
    • Set LangVersion compiler option to 13.0 (#24617) (Thanks @xtqqczze!)
    • -
    • Update metadata.json for PowerShell 7.5 RC1 release (#24589)
    • -
    • Update nuget publish to use Deploy Box (#24596)
    • -
    • Added Deploy Box Product Pathway to GitHub Release and NuGet Release Pipelines (#24583)
    • -
    • Update machine pool for copy blob and upload buildinfo stage (#24587)
    • -
    • Bump .NET 9 and dependencies (#24573)
    • -
    • Bump actions/dependency-review-action from 4.3.4 to 4.4.0 (#24503)
    • -
    • Bump actions/checkout from 4.2.1 to 4.2.2 (#24488)
    • -
    • Bump agrc/reminder-action from 1.0.14 to 1.0.15 (#24384)
    • -
    • Bump actions/upload-artifact from 4.4.0 to 4.4.3 (#24410)
    • -
    • Update branch for release (#24534)
    • -
    • Revert "Update package references (#24414)" (#24532)
    • -
    • Add a way to use only NuGet feed sources (#24528)
    • -
    • Update PSResourceGet to v1.1.0-RC2 (#24512) (Thanks @alerickson!)
    • -
    • Bump .NET to 9.0.100-rc.2.24474.11 (#24509)
    • -
    • Fix seed max value for Container Linux CI (#24510)
    • -
    • Update metadata.json for 7.2.24 and 7.4.6 releases (#24484)
    • -
    • Download package from package build for generating vpack (#24481)
    • -
    • Keep the roff file when gzipping it. (#24450)
    • -
    • Delete the msix blob if it's already there (#24353)
    • -
    • Add PMC mapping for debian 12 (bookworm) (#24413)
    • -
    • Checkin generated manpage (#24423)
    • -
    • Add CodeQL scanning to APIScan build (#24303)
    • -
    • Update package references (#24414)
    • -
    • Update vpack pipeline (#24281)
    • -
    • Bring changes from v7.5.0-preview.5 Release Branch to Master (#24369)
    • -
    • Bump agrc/create-reminder-action from 1.1.15 to 1.1.16 (#24375)
    • -
    • Add BaseUrl to buildinfo json file (#24376)
    • -
    • Update metadata.json (#24352)
    • -
    • Copy to static site instead of making blob public (#24269)
    • -
    • Update Microsoft.PowerShell.PSResourceGet to 1.1.0-preview2 (#24300) (Thanks @alerickson!)
    • -
    • add updated libicu dependency for debian packages (#24301)
    • -
    • add mapping to azurelinux repo (#24290)
    • -
    • Remove the MD5 branch in the strong name signing token calculation (#24288)
    • -
    • Bump .NET 9 to 9.0.100-rc.1.24452.12 (#24273)
    • -
    • Ensure the official build files CodeQL issues (#24278)
    • -
    • Update experimental-feature json files (#24271)
    • -
    • Make some release tests run in a hosted pools (#24270)
    • -
    • Do not build the exe for Global tool shim project (#24263)
    • -
    • Update and add new NuGet package sources for different environments. (#24264)
    • -
    • Bump skitionek/notify-microsoft-teams (#24261)
    • -
    • Create new pipeline for compliance (#24252)
    • -
    • Capture environment better (#24148)
    • -
    • Add specific path for issues in tsaconfig (#24244)
    • -
    • Use Managed Identity for APIScan authentication (#24243)
    • -
    • Add windows signing for pwsh.exe (#24219)
    • -
    • Bump super-linter/super-linter from 7.0.0 to 7.1.0 (#24223)
    • -
    • Update the URLs used in nuget.config files (#24203)
    • -
    • Check Create and Submit in vPack build by default (#24181)
    • -
    • Replace PSVersion source generator with incremental one (#23815) (Thanks @iSazonov!)
    • -
    • Save man files in /usr/share/man instead of /usr/local/share/man (#23855) (Thanks @rzippo!)
    • -
    • Bump super-linter/super-linter from 6.8.0 to 7.0.0 (#24169)
    • -
    - -
    - -### Documentation and Help Content - -- Updated Third Party Notices (#24666) -- Update `HelpInfoUri` for 7.5 (#24610) -- Update changelog for v7.4.6 release (#24496) -- Update to the latest NOTICES file (#24259) -- Update the changelog `preview.md` (#24213) -- Update changelog readme with 7.4 (#24182) (Thanks @ThomasNieto!) -- Fix Markdown linting error (#24204) -- Updated changelog for v7.2.23 (#24196) (Internal 32131) -- Update changelog and `metadata.json` for v7.4.5 release (#24183) -- Bring 7.2 changelogs back to master (#24158) - -[7.6.0-preview.1]: https://github.com/PowerShell/PowerShell/compare/v7.5.0-rc.1...v7.6.0-preview.1 +- Check in `7.6.md` after v7.6.0 release (#27063) +- Update changelog for release v7.5.5 (#27014) +- Add 7.4.14 changelog (#26998) +- Update `SECURITY.md` to remove email reporting option (#26653) +- Update changelog for the release v7.6.0-preview.6 (#26597) +- Explain the parameter `-UseNuGetOrg` in build documentation (#26507) (Thanks @logiclrd!) +- Update backport prompt (#26392) +- Add a backport prompt for copilot (#26383) +- Update `linux.md` documentation to reflect current CI build configuration (#26255) +- Add GitHub Copilot instruction files for PowerShell CI build system (#26253) +- Add documentation for publishing Pester test results in GitHub Actions (#26254) +- Remove Gitter from README (#26200) (Thanks @xtqqczze!) +- Remove nightly build status section from README.md (#26227) (Thanks @xtqqczze!) +- Update changelog for v7.5.4 and v7.4.13 (#26202) + +[7.7.0-preview.1]: https://github.com/PowerShell/PowerShell/compare/v7.6.0-rc.1...v7.7.0-preview.1 diff --git a/CHANGELOG/v7.7/dependencychanges.json b/CHANGELOG/v7.7/dependencychanges.json new file mode 100644 index 00000000000..21cd1577251 --- /dev/null +++ b/CHANGELOG/v7.7/dependencychanges.json @@ -0,0 +1,15 @@ +[ + { + "ChangeType": "NonSecurity", + "Branch": "master", + "PackageId": ".NET SDK", + "FromVersion": "11.0.100-preview.2.26159.112", + "ToVersion": "11.0.100-preview.3.26207.106", + "VulnerabilityId": [], + "Severity": [], + "VulnerableRanges": [], + "AdvisoryUrls": [], + "Justification": "Updated .NET SDK. Building with the latest SDK is required.", + "TimestampUtc": "2026-04-17T17:16:15.7099916Z" + } +] diff --git a/DotnetRuntimeMetadata.json b/DotnetRuntimeMetadata.json index 5c5d2190864..f8288b53b67 100644 --- a/DotnetRuntimeMetadata.json +++ b/DotnetRuntimeMetadata.json @@ -4,7 +4,7 @@ "quality": "daily", "qualityFallback": "preview", "packageVersionPattern": "9.0.0-preview.6", - "sdkImageVersion": "10.0.100-preview.6.25358.103", + "sdkImageVersion": "11.0.100-preview.3.26207.106", "nextChannel": "9.0.0-preview.7", "azureFeed": "", "sdkImageOverride": "" diff --git a/PowerShell.Common.props b/PowerShell.Common.props index 5dce6eac9a5..d71432aed2f 100644 --- a/PowerShell.Common.props +++ b/PowerShell.Common.props @@ -144,8 +144,8 @@ (c) Microsoft Corporation. PowerShell 7 - net10.0 - 13.0 + net11.0 + preview true true @@ -163,7 +163,7 @@ $(DefineConstants);CORECLR - true + true @@ -176,10 +176,43 @@ portable - - + + + + EnvironmentVariable;Global + false + false + + + + + Global + + + + true true + + + + AppLocal + + + + + true + true + + + + true portable diff --git a/README.md b/README.md index dade0207250..a7b31c475f8 100644 --- a/README.md +++ b/README.md @@ -56,26 +56,10 @@ Want to chat with other members of the PowerShell community? There are dozens of topic-specific channels on our community-driven PowerShell Virtual User Group, which you can join on: -* [Gitter](https://gitter.im/PowerShell/PowerShell) * [Discord](https://discord.gg/PowerShell) * [IRC](https://web.libera.chat/#powershell) on Libera.Chat * [Slack](https://aka.ms/psslack) -### Build status of nightly builds - -| Azure CI (Windows) | Azure CI (Linux) | Azure CI (macOS) | CodeFactor Grade | -|:-----------------------------------------|:-----------------------------------------------|:-----------------------------------------------|:-------------------------| -| [![windows-nightly-image][]][windows-nightly-site] | [![linux-nightly-image][]][linux-nightly-site] | [![macOS-nightly-image][]][macos-nightly-site] | [![cf-image][]][cf-site] | - -[windows-nightly-site]: https://powershell.visualstudio.com/PowerShell/_build?definitionId=32 -[linux-nightly-site]: https://powershell.visualstudio.com/PowerShell/_build?definitionId=23 -[macos-nightly-site]: https://powershell.visualstudio.com/PowerShell/_build?definitionId=24 -[windows-nightly-image]: https://powershell.visualstudio.com/PowerShell/_apis/build/status/PowerShell-CI-Windows-daily -[linux-nightly-image]: https://powershell.visualstudio.com/PowerShell/_apis/build/status/PowerShell-CI-linux-daily?branchName=master -[macOS-nightly-image]: https://powershell.visualstudio.com/PowerShell/_apis/build/status/PowerShell-CI-macos-daily?branchName=master -[cf-site]: https://www.codefactor.io/repository/github/powershell/powershell -[cf-image]: https://www.codefactor.io/repository/github/powershell/powershell/badge - ## Developing and Contributing Want to contribute to PowerShell? Please start with the [Contribution Guide][] to learn how to develop and contribute. diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt index 28a06a82744..4d033a0f682 100644 --- a/ThirdPartyNotices.txt +++ b/ThirdPartyNotices.txt @@ -17,7 +17,7 @@ required to debug changes to any libraries licensed under the GNU Lesser General --------------------------------------------------------- -Markdig.Signed 0.40.0 - BSD-2-Clause +Markdig.Signed 0.45.0 - BSD-2-Clause @@ -119,7 +119,7 @@ SOFTWARE. --------------------------------------------------------- -JsonSchema.Net 7.3.4 - MIT +JsonSchema.Net 7.4.0 - MIT Copyright (c) .NET Foundation and Contributors @@ -170,7 +170,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -Microsoft.Bcl.AsyncInterfaces 9.0.3 - MIT +Microsoft.Bcl.AsyncInterfaces 10.0.3 - MIT Copyright (c) 2021 @@ -193,7 +193,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -214,7 +214,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -231,36 +230,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -Microsoft.CodeAnalysis.Common 4.13.0 - MIT +Microsoft.CodeAnalysis.Common 5.0.0 - MIT (c) Microsoft Corporation @@ -280,7 +264,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -Microsoft.CodeAnalysis.CSharp 4.13.0 - MIT +Microsoft.CodeAnalysis.CSharp 5.0.0 - MIT (c) Microsoft Corporation @@ -305,7 +289,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -Microsoft.Extensions.ObjectPool 9.0.3 - MIT +Microsoft.Extensions.ObjectPool 10.0.3 - MIT Copyright Jorn Zaefferer @@ -395,7 +379,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -Microsoft.Win32.Registry.AccessControl 9.0.3 - MIT +Microsoft.Win32.Registry.AccessControl 10.0.3 - MIT Copyright (c) 2021 @@ -418,7 +402,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -439,7 +423,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -456,36 +439,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -Microsoft.Win32.SystemEvents 9.0.3 - MIT +Microsoft.Win32.SystemEvents 10.0.3 - MIT Copyright (c) 2021 @@ -508,7 +476,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -529,7 +497,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -546,36 +513,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -Microsoft.Windows.Compatibility 9.0.3 - MIT +Microsoft.Windows.Compatibility 10.0.3 - MIT (c) Microsoft Corporation @@ -594,7 +546,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -Newtonsoft.Json 13.0.3 - MIT +Newtonsoft.Json 13.0.4 - MIT Copyright James Newton-King 2008 @@ -628,7 +580,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -runtime.android-arm.runtime.native.System.IO.Ports 9.0.3 - MIT +runtime.android-arm.runtime.native.System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -651,7 +603,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -672,7 +624,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -689,36 +640,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -runtime.android-arm64.runtime.native.System.IO.Ports 9.0.3 - MIT +runtime.android-arm64.runtime.native.System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -741,7 +677,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -762,7 +698,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -779,36 +714,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -runtime.android-x64.runtime.native.System.IO.Ports 9.0.3 - MIT +runtime.android-x64.runtime.native.System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -831,7 +751,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -852,7 +772,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -869,36 +788,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -runtime.android-x86.runtime.native.System.IO.Ports 9.0.3 - MIT +runtime.android-x86.runtime.native.System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -921,7 +825,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -942,7 +846,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -959,36 +862,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -runtime.linux-arm.runtime.native.System.IO.Ports 9.0.3 - MIT +runtime.linux-arm.runtime.native.System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -1011,7 +899,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -1032,7 +920,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -1049,36 +936,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -runtime.linux-arm64.runtime.native.System.IO.Ports 9.0.3 - MIT +runtime.linux-arm64.runtime.native.System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -1101,7 +973,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -1122,7 +994,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -1139,36 +1010,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -runtime.linux-bionic-arm64.runtime.native.System.IO.Ports 9.0.3 - MIT +runtime.linux-bionic-arm64.runtime.native.System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -1191,7 +1047,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -1212,7 +1068,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -1229,36 +1084,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -runtime.linux-bionic-x64.runtime.native.System.IO.Ports 9.0.3 - MIT +runtime.linux-bionic-x64.runtime.native.System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -1281,7 +1121,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -1302,7 +1142,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -1319,36 +1158,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -runtime.linux-musl-arm.runtime.native.System.IO.Ports 9.0.3 - MIT +runtime.linux-musl-arm.runtime.native.System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -1371,7 +1195,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -1392,7 +1216,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -1409,36 +1232,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -runtime.linux-musl-arm64.runtime.native.System.IO.Ports 9.0.3 - MIT +runtime.linux-musl-arm64.runtime.native.System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -1461,7 +1269,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -1482,7 +1290,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -1499,36 +1306,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -runtime.linux-musl-x64.runtime.native.System.IO.Ports 9.0.3 - MIT +runtime.linux-musl-x64.runtime.native.System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -1551,7 +1343,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -1572,7 +1364,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -1589,36 +1380,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -runtime.linux-x64.runtime.native.System.IO.Ports 9.0.3 - MIT +runtime.linux-x64.runtime.native.System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -1641,7 +1417,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -1662,7 +1438,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -1679,36 +1454,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -runtime.maccatalyst-arm64.runtime.native.System.IO.Ports 9.0.3 - MIT +runtime.maccatalyst-arm64.runtime.native.System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -1731,7 +1491,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -1752,7 +1512,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -1769,36 +1528,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -runtime.maccatalyst-x64.runtime.native.System.IO.Ports 9.0.3 - MIT +runtime.maccatalyst-x64.runtime.native.System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -1821,7 +1565,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -1842,7 +1586,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -1859,30 +1602,15 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- @@ -1933,7 +1661,7 @@ SOFTWARE. --------------------------------------------------------- -runtime.native.System.IO.Ports 9.0.3 - MIT +runtime.native.System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -1956,7 +1684,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -1977,7 +1705,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -1994,36 +1721,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -runtime.osx-arm64.runtime.native.System.IO.Ports 9.0.3 - MIT +runtime.osx-arm64.runtime.native.System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -2046,7 +1758,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -2067,7 +1779,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -2084,36 +1795,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -runtime.osx-x64.runtime.native.System.IO.Ports 9.0.3 - MIT +runtime.osx-x64.runtime.native.System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -2136,7 +1832,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -2157,7 +1853,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -2174,36 +1869,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -System.CodeDom 9.0.3 - MIT +System.CodeDom 10.0.3 - MIT Copyright (c) 2021 @@ -2226,7 +1906,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -2247,7 +1927,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -2264,36 +1943,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -System.ComponentModel.Composition 9.0.3 - MIT +System.ComponentModel.Composition 10.0.3 - MIT Copyright (c) 2021 @@ -2316,7 +1980,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -2337,7 +2001,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -2354,36 +2017,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -System.ComponentModel.Composition.Registration 9.0.3 - MIT +System.ComponentModel.Composition.Registration 10.0.3 - MIT Copyright (c) 2021 @@ -2406,7 +2054,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -2427,7 +2075,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -2444,36 +2091,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -System.Configuration.ConfigurationManager 9.0.3 - MIT +System.Configuration.ConfigurationManager 10.0.3 - MIT Copyright (c) 2021 @@ -2496,7 +2128,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -2517,7 +2149,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -2534,36 +2165,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -System.Data.Odbc 9.0.3 - MIT +System.Data.Odbc 10.0.3 - MIT Copyright (c) 2021 @@ -2586,7 +2202,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -2607,7 +2223,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -2624,36 +2239,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -System.Data.OleDb 9.0.3 - MIT +System.Data.OleDb 10.0.3 - MIT Copyright (c) 2021 @@ -2676,7 +2276,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -2697,7 +2297,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -2714,30 +2313,15 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- @@ -2762,7 +2346,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -System.Diagnostics.EventLog 9.0.3 - MIT +System.Diagnostics.EventLog 10.0.3 - MIT Copyright (c) 2021 @@ -2785,7 +2369,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -2806,7 +2390,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -2823,36 +2406,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -System.Diagnostics.PerformanceCounter 9.0.3 - MIT +System.Diagnostics.PerformanceCounter 10.0.3 - MIT Copyright (c) 2021 @@ -2875,7 +2443,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -2896,7 +2464,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -2913,36 +2480,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -System.DirectoryServices 9.0.3 - MIT +System.DirectoryServices 10.0.3 - MIT Copyright (c) 2021 @@ -2965,7 +2517,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -2986,7 +2538,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -3003,36 +2554,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -System.DirectoryServices.AccountManagement 9.0.3 - MIT +System.DirectoryServices.AccountManagement 10.0.3 - MIT Copyright (c) 2021 @@ -3055,7 +2591,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -3076,7 +2612,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -3093,36 +2628,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -System.DirectoryServices.Protocols 9.0.3 - MIT +System.DirectoryServices.Protocols 10.0.3 - MIT Copyright (c) 2021 @@ -3145,7 +2665,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -3166,7 +2686,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -3183,36 +2702,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -System.Drawing.Common 9.0.3 - MIT +System.Drawing.Common 10.0.3 - MIT (c) Microsoft Corporation @@ -3247,7 +2751,7 @@ SOFTWARE. --------------------------------------------------------- -System.IO.Packaging 9.0.3 - MIT +System.IO.Packaging 10.0.3 - MIT Copyright (c) 2021 @@ -3270,7 +2774,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -3291,7 +2795,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -3308,36 +2811,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -System.IO.Ports 9.0.3 - MIT +System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -3360,7 +2848,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -3381,7 +2869,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -3398,36 +2885,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -System.Management 9.0.3 - MIT +System.Management 10.0.3 - MIT Copyright (c) 2021 @@ -3450,7 +2922,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -3471,7 +2943,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -3488,36 +2959,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -System.Net.Http.WinHttpHandler 9.0.3 - MIT +System.Net.Http.WinHttpHandler 10.0.3 - MIT Copyright (c) 2021 @@ -3540,7 +2996,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -3561,7 +3017,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -3578,72 +3033,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Private.ServiceModel 4.10.3 - MIT - - -(c) Microsoft Corporation -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -System.Reflection.Context 9.0.3 - MIT +System.Reflection.Context 10.0.3 - MIT Copyright (c) 2021 @@ -3666,7 +3070,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -3687,7 +3091,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -3704,36 +3107,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -System.Runtime.Caching 9.0.3 - MIT +System.Runtime.Caching 10.0.3 - MIT Copyright (c) 2021 @@ -3756,7 +3144,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -3777,7 +3165,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -3794,36 +3181,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -System.Security.Cryptography.Pkcs 9.0.3 - MIT +System.Security.Cryptography.Pkcs 10.0.3 - MIT Copyright (c) 2021 @@ -3846,7 +3218,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -3867,7 +3239,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -3884,36 +3255,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -System.Security.Cryptography.ProtectedData 9.0.3 - MIT +System.Security.Cryptography.ProtectedData 10.0.3 - MIT Copyright (c) 2021 @@ -3936,7 +3292,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -3957,7 +3313,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -3974,36 +3329,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -System.Security.Cryptography.Xml 9.0.3 - MIT +System.Security.Cryptography.Xml 10.0.3 - MIT Copyright (c) 2021 @@ -4026,7 +3366,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -4047,7 +3387,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -4064,36 +3403,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -System.Security.Permissions 9.0.3 - MIT +System.Security.Permissions 10.0.3 - MIT Copyright (c) 2021 @@ -4116,7 +3440,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -4137,7 +3461,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -4154,113 +3477,26 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.ServiceModel.Duplex 4.10.3 - MIT - - -(c) Microsoft Corporation -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.ServiceModel.Http 4.10.3 - MIT - - -(c) Microsoft Corporation -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -System.ServiceModel.NetTcp 4.10.3 - MIT +System.ServiceModel.Http 10.0.652802 - MIT (c) Microsoft Corporation Copyright (c) .NET Foundation and Contributors -Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) +Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) Provided The MIT License (MIT) @@ -4291,12 +3527,12 @@ SOFTWARE. --------------------------------------------------------- -System.ServiceModel.Primitives 4.10.3 - MIT +System.ServiceModel.NetFramingBase 10.0.652802 - MIT (c) Microsoft Corporation Copyright (c) .NET Foundation and Contributors -Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) +Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) Provided The MIT License (MIT) @@ -4327,12 +3563,12 @@ SOFTWARE. --------------------------------------------------------- -System.ServiceModel.Security 4.10.3 - MIT +System.ServiceModel.NetTcp 10.0.652802 - MIT (c) Microsoft Corporation Copyright (c) .NET Foundation and Contributors -Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) +Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) Provided The MIT License (MIT) @@ -4363,66 +3599,12 @@ SOFTWARE. --------------------------------------------------------- -System.ServiceModel.Syndication 9.0.3 - MIT +System.ServiceModel.Primitives 10.0.652802 - MIT -Copyright (c) 2021 -Copyright (c) Six Labors (c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass +Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) Provided The MIT License (MIT) @@ -4453,7 +3635,7 @@ SOFTWARE. --------------------------------------------------------- -System.ServiceProcess.ServiceController 9.0.3 - MIT +System.ServiceModel.Syndication 10.0.3 - MIT Copyright (c) 2021 @@ -4476,7 +3658,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -4497,7 +3679,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -4514,36 +3695,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -System.Speech 9.0.3 - MIT +System.ServiceProcess.ServiceController 10.0.3 - MIT Copyright (c) 2021 @@ -4566,7 +3732,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -4587,7 +3753,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -4604,36 +3769,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -System.Threading.AccessControl 9.0.3 - MIT +System.Speech 10.0.3 - MIT Copyright (c) 2021 @@ -4656,7 +3806,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -4677,7 +3827,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -4694,36 +3843,21 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -System.Web.Services.Description 8.1.1 - MIT +System.Web.Services.Description 8.1.2 - MIT (c) Microsoft Corporation @@ -4759,7 +3893,7 @@ SOFTWARE. --------------------------------------------------------- -System.Windows.Extensions 9.0.3 - MIT +System.Windows.Extensions 10.0.3 - MIT Copyright (c) 2021 @@ -4782,7 +3916,7 @@ Copyright (c) 2005-2020 Rich Felker Copyright (c) 2012-2021 Yann Collet Copyright (c) Microsoft Corporation Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -4803,7 +3937,6 @@ Copyright (c) The Internet Society (2003) Copyright (c) .NET Foundation Contributors (c) 1995-2024 Jean-loup Gailly and Mark Adler Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors Copyright (c) 2012 - present, Victor Zverovich Copyright (c) 2006 Jb Evain (jbevain@gmail.com) Copyright (c) 2008-2020 Advanced Micro Devices, Inc. @@ -4820,30 +3953,15 @@ Copyright (c) 1980, 1986, 1993 The Regents of the University of California Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- diff --git a/assets/AppxManifest.xml b/assets/AppxManifest.xml index 50a8c7af45d..dfcd95935d9 100644 --- a/assets/AppxManifest.xml +++ b/assets/AppxManifest.xml @@ -43,6 +43,7 @@ + diff --git a/assets/MicrosoftUpdate/RegisterMicrosoftUpdate.ps1 b/assets/MicrosoftUpdate/RegisterMicrosoftUpdate.ps1 deleted file mode 100644 index db756063da4..00000000000 --- a/assets/MicrosoftUpdate/RegisterMicrosoftUpdate.ps1 +++ /dev/null @@ -1,70 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -param( - [ValidateSet('Hang', 'Fail')] - $TestHook -) - -$waitTimeoutSeconds = 300 -switch ($TestHook) { - 'Hang' { - $waitTimeoutSeconds = 10 - $jobScript = { Start-Sleep -Seconds 600 } - } - 'Fail' { - $jobScript = { throw "This job script should fail" } - } - default { - $jobScript = { - # This registers Microsoft Update via a predifened GUID with the Windows Update Agent. - # https://learn.microsoft.com/windows/win32/wua_sdk/opt-in-to-microsoft-update - - $serviceManager = (New-Object -ComObject Microsoft.Update.ServiceManager) - $isRegistered = $serviceManager.QueryServiceRegistration('7971f918-a847-4430-9279-4a52d1efe18d').Service.IsRegisteredWithAu - - if (!$isRegistered) { - Write-Verbose -Verbose "Opting into Microsoft Update as the Autmatic Update Service" - # 7 is the combination of asfAllowPendingRegistration, asfAllowOnlineRegistration, asfRegisterServiceWithAU - # AU means Automatic Updates - $null = $serviceManager.AddService2('7971f918-a847-4430-9279-4a52d1efe18d', 7, '') - } - else { - Write-Verbose -Verbose "Microsoft Update is already registered for Automatic Updates" - } - - $isRegistered = $serviceManager.QueryServiceRegistration('7971f918-a847-4430-9279-4a52d1efe18d').Service.IsRegisteredWithAu - - # Return if it was successful, which is the opposite of Pending. - return $isRegistered - } - } -} - -Write-Verbose "Running job script: $jobScript" -Verbose -$job = Start-ThreadJob -ScriptBlock $jobScript - -Write-Verbose "Waiting on Job for $waitTimeoutSeconds seconds" -Verbose -$null = Wait-Job -Job $job -Timeout $waitTimeoutSeconds - -if ($job.State -ne 'Running') { - Write-Verbose "Job finished. State: $($job.State)" -Verbose - $result = Receive-Job -Job $job -Verbose - Write-Verbose "Result: $result" -Verbose - if ($result) { - Write-Verbose "Registration succeeded" -Verbose - exit 0 - } - else { - Write-Verbose "Registration failed" -Verbose - # at the time this was written, the MSI is ignoring the exit code - exit 1 - } -} -else { - Write-Verbose "Job timed out" -Verbose - Write-Verbose "Stopping Job. State: $($job.State)" -Verbose - Stop-Job -Job $job - # at the time this was written, the MSI is ignoring the exit code - exit 258 -} diff --git a/assets/macos-entitlements.plist b/assets/macos-entitlements.plist new file mode 100644 index 00000000000..9d534f4f4bf --- /dev/null +++ b/assets/macos-entitlements.plist @@ -0,0 +1,14 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.allow-dyld-environment-variables + + com.apple.security.cs.disable-library-validation + + + diff --git a/assets/wix/ExeLicense.rtf b/assets/wix/ExeLicense.rtf deleted file mode 100644 index 09b0b1402a5..00000000000 --- a/assets/wix/ExeLicense.rtf +++ /dev/null @@ -1,206 +0,0 @@ -{\rtf1\adeflang1025\ansi\ansicpg1252\uc1\adeff0\deff0\stshfdbch0\stshfloch31506\stshfhich31506\stshfbi31506\deflang1033\deflangfe1033\themelang1033\themelangfe0\themelangcs0{\fonttbl{\f0\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f34\fbidi \froman\fcharset0\fprq2{\*\panose 02040503050406030204}Cambria Math;} -{\f37\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;}{\flomajor\f31500\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} -{\fdbmajor\f31501\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fhimajor\f31502\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0302020204030204}Calibri Light;} -{\fbimajor\f31503\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\flominor\f31504\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} -{\fdbminor\f31505\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fhiminor\f31506\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;} -{\fbiminor\f31507\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f1032\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\f1033\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} -{\f1035\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f1036\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f1037\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} -{\f1038\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\f1039\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f1040\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);} -{\f1372\fbidi \froman\fcharset238\fprq2 Cambria Math CE;}{\f1373\fbidi \froman\fcharset204\fprq2 Cambria Math Cyr;}{\f1375\fbidi \froman\fcharset161\fprq2 Cambria Math Greek;}{\f1376\fbidi \froman\fcharset162\fprq2 Cambria Math Tur;} -{\f1379\fbidi \froman\fcharset186\fprq2 Cambria Math Baltic;}{\f1380\fbidi \froman\fcharset163\fprq2 Cambria Math (Vietnamese);}{\f1402\fbidi \fswiss\fcharset238\fprq2 Calibri CE;}{\f1403\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;} -{\f1405\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\f1406\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;}{\f1407\fbidi \fswiss\fcharset177\fprq2 Calibri (Hebrew);}{\f1408\fbidi \fswiss\fcharset178\fprq2 Calibri (Arabic);} -{\f1409\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;}{\f1410\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);}{\flomajor\f31508\fbidi \froman\fcharset238\fprq2 Times New Roman CE;} -{\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;} -{\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;} -{\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbmajor\f31518\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fdbmajor\f31519\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} -{\fdbmajor\f31521\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fdbmajor\f31522\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbmajor\f31523\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} -{\fdbmajor\f31524\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fdbmajor\f31525\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbmajor\f31526\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);} -{\fhimajor\f31528\fbidi \fswiss\fcharset238\fprq2 Calibri Light CE;}{\fhimajor\f31529\fbidi \fswiss\fcharset204\fprq2 Calibri Light Cyr;}{\fhimajor\f31531\fbidi \fswiss\fcharset161\fprq2 Calibri Light Greek;} -{\fhimajor\f31532\fbidi \fswiss\fcharset162\fprq2 Calibri Light Tur;}{\fhimajor\f31533\fbidi \fswiss\fcharset177\fprq2 Calibri Light (Hebrew);}{\fhimajor\f31534\fbidi \fswiss\fcharset178\fprq2 Calibri Light (Arabic);} -{\fhimajor\f31535\fbidi \fswiss\fcharset186\fprq2 Calibri Light Baltic;}{\fhimajor\f31536\fbidi \fswiss\fcharset163\fprq2 Calibri Light (Vietnamese);}{\fbimajor\f31538\fbidi \froman\fcharset238\fprq2 Times New Roman CE;} -{\fbimajor\f31539\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fbimajor\f31541\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbimajor\f31542\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;} -{\fbimajor\f31543\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fbimajor\f31544\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbimajor\f31545\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;} -{\fbimajor\f31546\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\flominor\f31548\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\flominor\f31549\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} -{\flominor\f31551\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flominor\f31552\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flominor\f31553\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} -{\flominor\f31554\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flominor\f31555\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flominor\f31556\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);} -{\fdbminor\f31558\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fdbminor\f31559\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fdbminor\f31561\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;} -{\fdbminor\f31562\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbminor\f31563\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbminor\f31564\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} -{\fdbminor\f31565\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbminor\f31566\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhiminor\f31568\fbidi \fswiss\fcharset238\fprq2 Calibri CE;} -{\fhiminor\f31569\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;}{\fhiminor\f31571\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\fhiminor\f31572\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;} -{\fhiminor\f31573\fbidi \fswiss\fcharset177\fprq2 Calibri (Hebrew);}{\fhiminor\f31574\fbidi \fswiss\fcharset178\fprq2 Calibri (Arabic);}{\fhiminor\f31575\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;} -{\fhiminor\f31576\fbidi \fswiss\fcharset163\fprq2 Calibri (Vietnamese);}{\fbiminor\f31578\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fbiminor\f31579\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} -{\fbiminor\f31581\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbiminor\f31582\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbiminor\f31583\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} -{\fbiminor\f31584\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbiminor\f31585\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbiminor\f31586\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}} -{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0; -\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;\red0\green0\blue0;\red0\green0\blue0;\chyperlink\ctint255\cshade255\red5\green99\blue193;\red96\green94\blue92;\red225\green223\blue221;} -{\*\defchp \f31506\fs24 }{\*\defpap \ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 }\noqfpromote {\stylesheet{\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 -\af0\afs24\alang1025 \ltrch\fcs0 \f31506\fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \snext0 \sqformat \spriority0 Normal;}{\*\cs10 \additive \sunhideused \spriority1 Default Paragraph Font;}{\* -\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv -\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31506\afs24\alang1025 \ltrch\fcs0 \f31506\fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \snext11 \ssemihidden \sunhideused Normal Table;}{\* -\cs15 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf19 \sbasedon10 \sunhideused \styrsid11998831 Hyperlink;}{\*\cs16 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \cf20\chshdng0\chcfpat0\chcbpat21 \sbasedon10 \ssemihidden \sunhideused \styrsid11998831 -Unresolved Mention;}{\s17\ql \li0\ri0\sb100\sa100\sbauto1\saauto1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs24\alang1025 \ltrch\fcs0 \fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 -\sbasedon0 \snext17 \ssemihidden \sunhideused \styrsid11998831 Normal (Web);}}{\*\pgptbl {\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}{\pgp\ipgp0\itap0\li0\ri0\sb0\sa0}}{\*\rsidtbl \rsid203718\rsid534261\rsid10121125\rsid11155465 -\rsid11998831\rsid15422468\rsid15613908}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim1}{\info{\author Travis Plunk}{\operator Travis Plunk} -{\creatim\yr2021\mo2\dy19\hr9\min23}{\revtim\yr2021\mo2\dy19\hr12\min33}{\version4}{\edmins5}{\nofpages1}{\nofwords75}{\nofchars430}{\nofcharsws504}{\vern6021}}{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2003/wordml}} -\paperw12240\paperh15840\margl1440\margr1440\margt1440\margb1440\gutter0\ltrsect -\widowctrl\ftnbj\aenddoc\trackmoves0\trackformatting1\donotembedsysfont1\relyonvml0\donotembedlingdata0\grfdocevents0\validatexml1\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors1\noxlattoyen -\expshrtn\noultrlspc\dntblnsbdb\nospaceforul\formshade\horzdoc\dgmargin\dghspace180\dgvspace180\dghorigin1440\dgvorigin1440\dghshow1\dgvshow1 -\jexpand\viewkind1\viewscale100\pgbrdrhead\pgbrdrfoot\splytwnine\ftnlytwnine\htmautsp\nolnhtadjtbl\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\allowfieldendsel\wrppunct -\asianbrkrule\rsidroot11998831\newtblstyruls\nogrowautofit\usenormstyforlist\noindnmbrts\felnbrelev\nocxsptable\indrlsweleven\noafcnsttbl\afelev\utinl\hwelev\spltpgpar\notcvasp\notbrkcnstfrctbl\notvatxbx\krnprsnet\cachedcolbal \nouicompat \fet0 -{\*\wgrffmtfilter 2450}\nofeaturethrottle1\ilfomacatclnup0\ltrpar \sectd \ltrsect\linex0\endnhere\sectlinegrid360\sectdefaultcl\sftnbj {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl2\pnucltr\pnstart1\pnindent720\pnhang -{\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl6\pnlcltr\pnstart1\pnindent720\pnhang -{\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}} -\pard\plain \ltrpar\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid15422468 \rtlch\fcs1 \af0\afs24\alang1025 \ltrch\fcs0 \f31506\fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \af0 -\ltrch\fcs0 \insrsid15422468\charrsid15422468 This application is licensed under the\~}{\field\fldedit{\*\fldinst {\rtlch\fcs1 \af0 \ltrch\fcs0 \insrsid11155465 HYPERLINK "https://aka.ms/PowerShellLicense"}{\rtlch\fcs1 \af0 \ltrch\fcs0 -\insrsid11155465\charrsid15422468 {\*\datafield -00d0c9ea79f9bace118c8200aa004ba90b0200000003000000e0c9ea79f9bace118c8200aa004ba90b5a000000680074007400700073003a002f002f0061006b0061002e006d0073002f0050006f007700650072005300680065006c006c004c006900630065006e00730065000000795881f43b1d7f48af2c825dc4852763 -0000000085ab000000}}}{\fldrslt {\rtlch\fcs1 \af0 \ltrch\fcs0 \cs15\ul\cf19\insrsid15422468\charrsid15422468 MIT License}}}\sectd \ltrsect\linex0\endnhere\sectlinegrid360\sectdefaultcl\sftnbj {\rtlch\fcs1 \af0 \ltrch\fcs0 \insrsid15422468\charrsid15422468 -. -\par You may review the\~}{\field\fldedit{\*\fldinst {\rtlch\fcs1 \af0 \ltrch\fcs0 \insrsid11155465 HYPERLINK "https://aka.ms/PowerShellThirdPartyNotices"}{\rtlch\fcs1 \af0 \ltrch\fcs0 \insrsid11155465\charrsid15422468 {\*\datafield -00d0c9ea79f9bace118c8200aa004ba90b0200000003000000e0c9ea79f9bace118c8200aa004ba90b6e000000680074007400700073003a002f002f0061006b0061002e006d0073002f0050006f007700650072005300680065006c006c0054006800690072006400500061007200740079004e006f007400690063006500 -73000000795881f43b1d7f48af2c825dc48527630000000085ab000000}}}{\fldrslt {\rtlch\fcs1 \af0 \ltrch\fcs0 \cs15\ul\cf19\insrsid15422468\charrsid15422468 ThirdPartyNotices}}}\sectd \ltrsect\linex0\endnhere\sectlinegrid360\sectdefaultcl\sftnbj {\rtlch\fcs1 \af0 -\ltrch\fcs0 \insrsid15422468\charrsid15422468 . -\par }\pard \ltrpar\ql \li0\ri0\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 {\rtlch\fcs1 \af0 \ltrch\fcs0 \insrsid15422468\charrsid15422468 This application collects telemetry under the\~}{\field\fldedit{\*\fldinst {\rtlch\fcs1 -\af0 \ltrch\fcs0 \insrsid534261 HYPERLINK "https://aka.ms/PrivacyPolicy"}{\rtlch\fcs1 \af0 \ltrch\fcs0 \insrsid534261 {\*\datafield -00d0c9ea79f9bace118c8200aa004ba90b0200000003000000e0c9ea79f9bace118c8200aa004ba90b52000000680074007400700073003a002f002f0061006b0061002e006d0073002f00500072006900760061006300790050006f006c006900630079000000795881f43b1d7f48af2c825dc48527630000000085ab0000} -}}{\fldrslt {\rtlch\fcs1 \af0 \ltrch\fcs0 \cs15\ul\cf19\insrsid15422468\charrsid15422468 Microsoft Privacy Statement}}}\sectd \ltrsect\linex0\endnhere\sectlinegrid360\sectdefaultcl\sftnbj {\rtlch\fcs1 \af0 \ltrch\fcs0 \insrsid15422468\charrsid15422468 -. To opt out, see\~}{\field\fldedit{\*\fldinst {\rtlch\fcs1 \af0 \ltrch\fcs0 \insrsid11155465 HYPERLINK "https://aka.ms/PowerShellTelemetryOptOut" \\o "Original URL:https://github.com/PowerShell/PowerS -hell/blob/master/README.md#telemetryClick to follow link."}{\rtlch\fcs1 \af0 \ltrch\fcs0 \insrsid11155465\charrsid15422468 {\*\datafield -10d0c9ea79f9bace118c8200aa004ba90b0200000003000000e0c9ea79f9bace118c8200aa004ba90b6a000000680074007400700073003a002f002f0061006b0061002e006d0073002f0050006f007700650072005300680065006c006c00540065006c0065006d0065007400720079004f00700074004f00750074000000 -795881f43b1d7f48af2c825dc48527630000000085ab000000}}}{\fldrslt {\rtlch\fcs1 \af0 \ltrch\fcs0 \cs15\ul\cf19\insrsid15422468\charrsid15422468 here}}}\sectd \ltrsect\linex0\endnhere\sectlinegrid360\sectdefaultcl\sftnbj {\rtlch\fcs1 \af0 \ltrch\fcs0 -\insrsid15422468\charrsid15422468 .}{\rtlch\fcs1 \af0 \ltrch\fcs0 \insrsid11998831 -\par }{\*\themedata 504b030414000600080000002100e9de0fbfff0000001c020000130000005b436f6e74656e745f54797065735d2e786d6cac91cb4ec3301045f748fc83e52d4a -9cb2400825e982c78ec7a27cc0c8992416c9d8b2a755fbf74cd25442a820166c2cd933f79e3be372bd1f07b5c3989ca74aaff2422b24eb1b475da5df374fd9ad -5689811a183c61a50f98f4babebc2837878049899a52a57be670674cb23d8e90721f90a4d2fa3802cb35762680fd800ecd7551dc18eb899138e3c943d7e503b6 -b01d583deee5f99824e290b4ba3f364eac4a430883b3c092d4eca8f946c916422ecab927f52ea42b89a1cd59c254f919b0e85e6535d135a8de20f20b8c12c3b0 -0c895fcf6720192de6bf3b9e89ecdbd6596cbcdd8eb28e7c365ecc4ec1ff1460f53fe813d3cc7f5b7f020000ffff0300504b030414000600080000002100a5d6 -a7e7c0000000360100000b0000005f72656c732f2e72656c73848fcf6ac3300c87ef85bd83d17d51d2c31825762fa590432fa37d00e1287f68221bdb1bebdb4f -c7060abb0884a4eff7a93dfeae8bf9e194e720169aaa06c3e2433fcb68e1763dbf7f82c985a4a725085b787086a37bdbb55fbc50d1a33ccd311ba548b6309512 -0f88d94fbc52ae4264d1c910d24a45db3462247fa791715fd71f989e19e0364cd3f51652d73760ae8fa8c9ffb3c330cc9e4fc17faf2ce545046e37944c69e462 -a1a82fe353bd90a865aad41ed0b5b8f9d6fd010000ffff0300504b0304140006000800000021006b799616830000008a0000001c0000007468656d652f746865 -6d652f7468656d654d616e616765722e786d6c0ccc4d0ac3201040e17da17790d93763bb284562b2cbaebbf600439c1a41c7a0d29fdbd7e5e38337cedf14d59b -4b0d592c9c070d8a65cd2e88b7f07c2ca71ba8da481cc52c6ce1c715e6e97818c9b48d13df49c873517d23d59085adb5dd20d6b52bd521ef2cdd5eb9246a3d8b -4757e8d3f729e245eb2b260a0238fd010000ffff0300504b030414000600080000002100b6f4679893070000c9200000160000007468656d652f7468656d652f -7468656d65312e786d6cec59cd8b1bc915bf07f23f347d97f5d5ad8fc1f2a24fcfda33b6b164873dd648a5eef2547789aad28cc56208de532e81c026e49085bd -ed21842cecc22eb9e48f31d8249b3f22afaa5bdd5552c99e191c3061463074977eefd5afde7bf5de53d5ddcf5e26d4bbc05c1096f6fcfa9d9aefe174ce16248d -7afeb3d9a4d2f13d2151ba4094a5b8e76fb0f03fbbf7eb5fdd454732c609f6403e1547a8e7c752ae8eaa5531876124eeb0154ee1bb25e30992f0caa3ea82a34b -d09bd06aa3566b55134452df4b51026a1f2f97648ebd9952e9dfdb2a1f53784da5500373caa74a35b6243476715e5708b11143cabd0b447b3eccb3609733fc52 -fa1e4542c2173dbfa6fffceabdbb5574940b517940d6909be8bf5c2e17589c37f49c3c3a2b260d823068f50bfd1a40e53e6edc1eb7c6ad429f06a0f91c569a71 -b175b61bc320c71aa0ecd1a17bd41e35eb16ded0dfdce3dc0fd5c7c26b50a63fd8c34f2643b0a285d7a00c1feee1c3417730b2f56b50866fede1dbb5fe28685b -fa3528a6243ddf43d7c25673b85d6d0159327aec8477c360d26ee4ca4b144443115d6a8a254be5a1584bd00bc6270050408a24493db959e1259a43140f112567 -9c7827248a21f056286502866b8ddaa4d684ffea13e827ed5174849121ad780113b137a4f87862cec94af6fc07a0d537206f7ffef9cdeb1fdfbcfee9cd575fbd -79fdf77c6eadca923b466964cafdf2dd1ffef3cd6fbd7ffff0ed2f5fff319b7a172f4cfcbbbffdeedd3ffef93ef5b0e2d2146ffff4fdbb1fbf7ffbe7dfffebaf -5f3bb4f7393a33e1339260e13dc297de5396c0021dfcf119bf9ec42c46c494e8a791402952b338f48f656ca11f6d10450edc00db767cce21d5b880f7d72f2cc2 -d398af2571687c182716f094313a60dc6985876a2ec3ccb3751ab927e76b13f714a10bd7dc43945a5e1eaf579063894be530c616cd2714a5124538c5d253dfb1 -738c1dabfb8210cbaea764ce99604be97d41bc01224e93ccc899154da5d03149c02f1b1741f0b7659bd3e7de8051d7aa47f8c246c2de40d4417e86a965c6fb68 -2d51e252394309350d7e8264ec2239ddf0b9891b0b099e8e3065de78818570c93ce6b05ec3e90f21cdb8dd7e4a37898de4929cbb749e20c64ce4889d0f6394ac -5cd829496313fbb938871045de13265df05366ef10f50e7e40e941773f27d872f787b3c133c8b026a53240d4376beef0e57dccacf89d6ee8126157aae9f3c44a -b17d4e9cd131584756689f604cd1255a60ec3dfbdcc160c05696cd4bd20f62c82ac7d815580f901dabea3dc5027a25d5dcece7c91322ac909de2881de073bad9 -493c1b9426881fd2fc08bc6eda7c0ca52e7105c0633a3f37818f08f480102f4ea33c16a0c308ee835a9fc4c82a60ea5db8e375c32dff5d658fc1be7c61d1b8c2 -be04197c6d1948eca6cc7b6d3343d49aa00c9819822ec3956e41c4727f29a28aab165b3be596f6a62ddd00dd91d5f42424fd6007b4d3fb84ffbbde073a8cb77f -f9c6b10f3e4ebfe3566c25ab6b763a8792c9f14e7f7308b7dbd50c195f904fbfa919a175fa04431dd9cf58b73dcd6d4fe3ffdff73487f6f36d2773a8dfb8ed64 -7ce8306e3b99fc70e5e3743265f3027d8d3af0c80e7af4b14f72f0d46749289dca0dc527421ffc08f83db398c0a092d3279eb838055cc5f0a8ca1c4c60e1228e -b48cc799fc0d91f134462b381daafb4a492472d591f0564cc0a1911e76ea5678ba4e4ed9223becacd7d5c16656590592e5782d2cc6e1a04a66e856bb3cc02bd4 -6bb6913e68dd1250b2d721614c6693683a48b4b783ca48fa58178ce620a157f65158741d2c3a4afdd6557b2c805ae115f8c1edc1cff49e1f06200242701e07cd -f942f92973f5d6bbda991fd3d3878c69450034d8db08283ddd555c0f2e4fad2e0bb52b78da2261849b4d425b46377822869fc17974aad1abd0b8aeafbba54b2d -7aca147a3e08ad9246bbf33e1637f535c8ede6069a9a9982a6de65cf6f35430899395af5fc251c1ac363b282d811ea3717a211dcbccc25cf36fc4d32cb8a0b39 -4222ce0cae934e960d122231f728497abe5a7ee1069aea1ca2b9d51b90103e59725d482b9f1a3970baed64bc5ce2b934dd6e8c284b67af90e1b35ce1fc568bdf -1cac24d91adc3d8d1797de195df3a708422c6cd795011744c0dd413db3e682c0655891c8caf8db294c79da356fa3740c65e388ae62945714339967709dca0b3a -faadb081f196af190c6a98242f8467912ab0a651ad6a5a548d8cc3c1aafb6121653923699635d3ca2aaa6abab39835c3b60cecd8f26645de60b53531e434b3c2 -67a97b37e576b7b96ea74f28aa0418bcb09fa3ea5ea12018d4cac92c6a8af17e1a56393b1fb56bc776811fa07695226164fdd656ed8edd8a1ae19c0e066f54f9 -416e376a6168b9ed2bb5a5f5adb979b1cdce5e40f2184197bba6526857c2c92e47d0104d754f92a50dd8222f65be35e0c95b73d2f3bfac85fd60d80887955a27 -1c57826650ab74c27eb3d20fc3667d1cd66ba341e31514161927f530bbb19fc00506dde4f7f67a7cefee3ed9ded1dc99b3a4caf4dd7c5513d777f7f5c6e1bb7b -8f40d2f9b2d598749bdd41abd26df627956034e854bac3d6a0326a0ddba3c9681876ba9357be77a1c141bf390c5ae34ea5551f0e2b41aba6e877ba9576d068f4 -8376bf330efaaff23606569ea58fdc16605ecdebde7f010000ffff0300504b0304140006000800000021000dd1909fb60000001b010000270000007468656d65 -2f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73848f4d0ac2301484f78277086f6fd3ba109126dd88d0add40384e4350d36 -3f2451eced0dae2c082e8761be9969bb979dc9136332de3168aa1a083ae995719ac16db8ec8e4052164e89d93b64b060828e6f37ed1567914b284d262452282e -3198720e274a939cd08a54f980ae38a38f56e422a3a641c8bbd048f7757da0f19b017cc524bd62107bd5001996509affb3fd381a89672f1f165dfe514173d985 -0528a2c6cce0239baa4c04ca5bbabac4df000000ffff0300504b01022d0014000600080000002100e9de0fbfff0000001c020000130000000000000000000000 -0000000000005b436f6e74656e745f54797065735d2e786d6c504b01022d0014000600080000002100a5d6a7e7c0000000360100000b00000000000000000000 -000000300100005f72656c732f2e72656c73504b01022d00140006000800000021006b799616830000008a0000001c0000000000000000000000000019020000 -7468656d652f7468656d652f7468656d654d616e616765722e786d6c504b01022d0014000600080000002100b6f4679893070000c92000001600000000000000 -000000000000d60200007468656d652f7468656d652f7468656d65312e786d6c504b01022d00140006000800000021000dd1909fb60000001b01000027000000 -000000000000000000009d0a00007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73504b050600000000050005005d010000980b00000000} -{\*\colorschememapping 3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d3822207374616e64616c6f6e653d22796573223f3e0d0a3c613a636c724d -617020786d6c6e733a613d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f64726177696e676d6c2f323030362f6d6169 -6e22206267313d226c743122207478313d22646b3122206267323d226c743222207478323d22646b322220616363656e74313d22616363656e74312220616363 -656e74323d22616363656e74322220616363656e74333d22616363656e74332220616363656e74343d22616363656e74342220616363656e74353d22616363656e74352220616363656e74363d22616363656e74362220686c696e6b3d22686c696e6b2220666f6c486c696e6b3d22666f6c486c696e6b222f3e} -{\*\latentstyles\lsdstimax376\lsdlockeddef0\lsdsemihiddendef0\lsdunhideuseddef0\lsdqformatdef0\lsdprioritydef99{\lsdlockedexcept \lsdqformat1 \lsdpriority0 \lsdlocked0 Normal;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 1; -\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 2;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 3;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 4; -\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 5;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 6;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 7; -\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 8;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 1; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 5; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 7;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 8;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index 9; -\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 1;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 2;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 3; -\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 4;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 5;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 6; -\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 7;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 8;\lsdsemihidden1 \lsdunhideused1 \lsdpriority39 \lsdlocked0 toc 9;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal Indent; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 header;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footer; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 index heading;\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority35 \lsdlocked0 caption;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of figures; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 envelope return;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 footnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation reference; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 line number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 page number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote reference;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 endnote text; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 table of authorities;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 macro;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 toa heading;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 3; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 3; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Bullet 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 3; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 4;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Number 5;\lsdqformat1 \lsdpriority10 \lsdlocked0 Title;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Closing; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Signature;\lsdsemihidden1 \lsdunhideused1 \lsdpriority1 \lsdlocked0 Default Paragraph Font;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 4; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 List Continue 5;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Message Header;\lsdqformat1 \lsdpriority11 \lsdlocked0 Subtitle;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Salutation; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Date;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text First Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Note Heading; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Body Text Indent 3; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Block Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 FollowedHyperlink;\lsdqformat1 \lsdpriority22 \lsdlocked0 Strong; -\lsdqformat1 \lsdpriority20 \lsdlocked0 Emphasis;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Document Map;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Plain Text;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 E-mail Signature; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Top of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Bottom of Form;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Normal (Web);\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Acronym; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Address;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Cite;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Code;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Definition; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Keyboard;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Preformatted;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Sample;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Typewriter; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 HTML Variable;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 annotation subject;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 No List;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 1; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 2;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Outline List 3;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Balloon Text;\lsdpriority39 \lsdlocked0 Table Grid; -\lsdsemihidden1 \lsdlocked0 Placeholder Text;\lsdqformat1 \lsdpriority1 \lsdlocked0 No Spacing;\lsdpriority60 \lsdlocked0 Light Shading;\lsdpriority61 \lsdlocked0 Light List;\lsdpriority62 \lsdlocked0 Light Grid; -\lsdpriority63 \lsdlocked0 Medium Shading 1;\lsdpriority64 \lsdlocked0 Medium Shading 2;\lsdpriority65 \lsdlocked0 Medium List 1;\lsdpriority66 \lsdlocked0 Medium List 2;\lsdpriority67 \lsdlocked0 Medium Grid 1;\lsdpriority68 \lsdlocked0 Medium Grid 2; -\lsdpriority69 \lsdlocked0 Medium Grid 3;\lsdpriority70 \lsdlocked0 Dark List;\lsdpriority71 \lsdlocked0 Colorful Shading;\lsdpriority72 \lsdlocked0 Colorful List;\lsdpriority73 \lsdlocked0 Colorful Grid;\lsdpriority60 \lsdlocked0 Light Shading Accent 1; -\lsdpriority61 \lsdlocked0 Light List Accent 1;\lsdpriority62 \lsdlocked0 Light Grid Accent 1;\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 1;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 1;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 1; -\lsdsemihidden1 \lsdlocked0 Revision;\lsdqformat1 \lsdpriority34 \lsdlocked0 List Paragraph;\lsdqformat1 \lsdpriority29 \lsdlocked0 Quote;\lsdqformat1 \lsdpriority30 \lsdlocked0 Intense Quote;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 1; -\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 1;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 1;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 1;\lsdpriority70 \lsdlocked0 Dark List Accent 1;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 1; -\lsdpriority72 \lsdlocked0 Colorful List Accent 1;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 1;\lsdpriority60 \lsdlocked0 Light Shading Accent 2;\lsdpriority61 \lsdlocked0 Light List Accent 2;\lsdpriority62 \lsdlocked0 Light Grid Accent 2; -\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 2;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 2;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 2;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 2; -\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 2;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 2;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 2;\lsdpriority70 \lsdlocked0 Dark List Accent 2;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 2; -\lsdpriority72 \lsdlocked0 Colorful List Accent 2;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 2;\lsdpriority60 \lsdlocked0 Light Shading Accent 3;\lsdpriority61 \lsdlocked0 Light List Accent 3;\lsdpriority62 \lsdlocked0 Light Grid Accent 3; -\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 3;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 3;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 3;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 3; -\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 3;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 3;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 3;\lsdpriority70 \lsdlocked0 Dark List Accent 3;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 3; -\lsdpriority72 \lsdlocked0 Colorful List Accent 3;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 3;\lsdpriority60 \lsdlocked0 Light Shading Accent 4;\lsdpriority61 \lsdlocked0 Light List Accent 4;\lsdpriority62 \lsdlocked0 Light Grid Accent 4; -\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 4;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 4;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 4;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 4; -\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 4;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 4;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 4;\lsdpriority70 \lsdlocked0 Dark List Accent 4;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 4; -\lsdpriority72 \lsdlocked0 Colorful List Accent 4;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 4;\lsdpriority60 \lsdlocked0 Light Shading Accent 5;\lsdpriority61 \lsdlocked0 Light List Accent 5;\lsdpriority62 \lsdlocked0 Light Grid Accent 5; -\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 5;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 5;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 5;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 5; -\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 5;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 5;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 5;\lsdpriority70 \lsdlocked0 Dark List Accent 5;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 5; -\lsdpriority72 \lsdlocked0 Colorful List Accent 5;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 5;\lsdpriority60 \lsdlocked0 Light Shading Accent 6;\lsdpriority61 \lsdlocked0 Light List Accent 6;\lsdpriority62 \lsdlocked0 Light Grid Accent 6; -\lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 6;\lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 6;\lsdpriority65 \lsdlocked0 Medium List 1 Accent 6;\lsdpriority66 \lsdlocked0 Medium List 2 Accent 6; -\lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 6;\lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 6;\lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 6;\lsdpriority70 \lsdlocked0 Dark List Accent 6;\lsdpriority71 \lsdlocked0 Colorful Shading Accent 6; -\lsdpriority72 \lsdlocked0 Colorful List Accent 6;\lsdpriority73 \lsdlocked0 Colorful Grid Accent 6;\lsdqformat1 \lsdpriority19 \lsdlocked0 Subtle Emphasis;\lsdqformat1 \lsdpriority21 \lsdlocked0 Intense Emphasis; -\lsdqformat1 \lsdpriority31 \lsdlocked0 Subtle Reference;\lsdqformat1 \lsdpriority32 \lsdlocked0 Intense Reference;\lsdqformat1 \lsdpriority33 \lsdlocked0 Book Title;\lsdsemihidden1 \lsdunhideused1 \lsdpriority37 \lsdlocked0 Bibliography; -\lsdsemihidden1 \lsdunhideused1 \lsdqformat1 \lsdpriority39 \lsdlocked0 TOC Heading;\lsdpriority41 \lsdlocked0 Plain Table 1;\lsdpriority42 \lsdlocked0 Plain Table 2;\lsdpriority43 \lsdlocked0 Plain Table 3;\lsdpriority44 \lsdlocked0 Plain Table 4; -\lsdpriority45 \lsdlocked0 Plain Table 5;\lsdpriority40 \lsdlocked0 Grid Table Light;\lsdpriority46 \lsdlocked0 Grid Table 1 Light;\lsdpriority47 \lsdlocked0 Grid Table 2;\lsdpriority48 \lsdlocked0 Grid Table 3;\lsdpriority49 \lsdlocked0 Grid Table 4; -\lsdpriority50 \lsdlocked0 Grid Table 5 Dark;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 1; -\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 1;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 1;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 1; -\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 1;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 2;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 2; -\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 2;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 2; -\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 3;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 3;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 3;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 3; -\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 3;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 4; -\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 4;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 4;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 4;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 4; -\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 4;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 5; -\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 5;\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 5;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 5; -\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 5;\lsdpriority46 \lsdlocked0 Grid Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 Grid Table 2 Accent 6;\lsdpriority48 \lsdlocked0 Grid Table 3 Accent 6; -\lsdpriority49 \lsdlocked0 Grid Table 4 Accent 6;\lsdpriority50 \lsdlocked0 Grid Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 Grid Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 Grid Table 7 Colorful Accent 6; -\lsdpriority46 \lsdlocked0 List Table 1 Light;\lsdpriority47 \lsdlocked0 List Table 2;\lsdpriority48 \lsdlocked0 List Table 3;\lsdpriority49 \lsdlocked0 List Table 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark; -\lsdpriority51 \lsdlocked0 List Table 6 Colorful;\lsdpriority52 \lsdlocked0 List Table 7 Colorful;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 1;\lsdpriority47 \lsdlocked0 List Table 2 Accent 1;\lsdpriority48 \lsdlocked0 List Table 3 Accent 1; -\lsdpriority49 \lsdlocked0 List Table 4 Accent 1;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 1;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 1;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 1; -\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 2;\lsdpriority47 \lsdlocked0 List Table 2 Accent 2;\lsdpriority48 \lsdlocked0 List Table 3 Accent 2;\lsdpriority49 \lsdlocked0 List Table 4 Accent 2; -\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 2;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 2;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 2;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 3; -\lsdpriority47 \lsdlocked0 List Table 2 Accent 3;\lsdpriority48 \lsdlocked0 List Table 3 Accent 3;\lsdpriority49 \lsdlocked0 List Table 4 Accent 3;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 3; -\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 3;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 3;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 4;\lsdpriority47 \lsdlocked0 List Table 2 Accent 4; -\lsdpriority48 \lsdlocked0 List Table 3 Accent 4;\lsdpriority49 \lsdlocked0 List Table 4 Accent 4;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 4;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 4; -\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 4;\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 5;\lsdpriority47 \lsdlocked0 List Table 2 Accent 5;\lsdpriority48 \lsdlocked0 List Table 3 Accent 5; -\lsdpriority49 \lsdlocked0 List Table 4 Accent 5;\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 5;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 5;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 5; -\lsdpriority46 \lsdlocked0 List Table 1 Light Accent 6;\lsdpriority47 \lsdlocked0 List Table 2 Accent 6;\lsdpriority48 \lsdlocked0 List Table 3 Accent 6;\lsdpriority49 \lsdlocked0 List Table 4 Accent 6; -\lsdpriority50 \lsdlocked0 List Table 5 Dark Accent 6;\lsdpriority51 \lsdlocked0 List Table 6 Colorful Accent 6;\lsdpriority52 \lsdlocked0 List Table 7 Colorful Accent 6;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Mention; -\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Hyperlink;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Hashtag;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Unresolved Mention;\lsdsemihidden1 \lsdunhideused1 \lsdlocked0 Smart Link;}}{\*\datastore }} \ No newline at end of file diff --git a/assets/wix/Product.wxs b/assets/wix/Product.wxs deleted file mode 100644 index 0b525288ae1..00000000000 --- a/assets/wix/Product.wxs +++ /dev/null @@ -1,654 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - not INSTALLFOLDER and PREVIOUS_INSTALLFOLDER - - - - - - - - not ADD_PATH and PREVIOUS_ADD_PATH - - - not ADD_PATH - - - ADD_PATH<>1 - - - not ADD_PATH - - - - - - - - not REGISTER_MANIFEST and PREVIOUS_REGISTER_MANIFEST - - - not REGISTER_MANIFEST - - - REGISTER_MANIFEST<>1 - - - not REGISTER_MANIFEST - - - - - - - - not ENABLE_PSREMOTING and PREVIOUS_ENABLE_PSREMOTING - - - ENABLE_PSREMOTING<>1 - - - not ENABLE_PSREMOTING - - - - - - - - not DISABLE_TELEMETRY and PREVIOUS_DISABLE_TELEMETRY - - - DISABLE_TELEMETRY<>1 - - - not DISABLE_TELEMETRY - - - - - - - - not ADD_EXPLORER_CONTEXT_MENU_OPENPOWERSHELL and PREVIOUS_ADD_EXPLORER_CONTEXT_MENU_OPENPOWERSHELL - - - ADD_EXPLORER_CONTEXT_MENU_OPENPOWERSHELL<>1 - - - not ADD_EXPLORER_CONTEXT_MENU_OPENPOWERSHELL - - - - - - - - not ADD_FILE_CONTEXT_MENU_RUNPOWERSHELL and PREVIOUS_ADD_FILE_CONTEXT_MENU_RUNPOWERSHELL - - - ADD_FILE_CONTEXT_MENU_RUNPOWERSHELL<>1 - - - not ADD_FILE_CONTEXT_MENU_RUNPOWERSHELL - - - - - - - - not USE_MU and PREVIOUS_USE_MU - - - not USE_MU - - - USE_MU<>1 - - - not USE_MU - - - - - - - - not ENABLE_MU and PREVIOUS_ENABLE_MU - - - not ENABLE_MU - - - ENABLE_MU<>1 - - - not ENABLE_MU - - - - - - - - - - - - - Installed AND NOT UPGRADINGPRODUCTCODE - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1 - - - LAUNCHAPPONEXIT=1 - - - - 1 OR VersionNT >= 603 ]]> - - - = 602 ]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - USE_MU=1 - - - - - - - - - - - - - - - - - - - - - - - - - DISABLE_TELEMETRY=1 - - - - - ADD_PATH=1 - - - - - - - - - ADD_EXPLORER_CONTEXT_MENU_OPENPOWERSHELL=1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ADD_FILE_CONTEXT_MENU_RUNPOWERSHELL=1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - The application is distributed under the MIT license.]]> - - - Please review the ThirdPartyNotices.txt]]> - - - - - - - 1 - - - - - - - - - - - - - - - - - - - - "1"]]> - "1" AND USE_MU="1"]]> - - - See the Microsoft Update FAQ]]> - - - Read the Microsoft Update Privacy Statement]]> - - - - - - - 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1 - "1"]]> - - NOT Installed - Installed AND PATCH - - 1 - 1 - 1 - 1 - - 1 - 1 - NOT WIXUI_DONTVALIDATEPATH - "1"]]> - WIXUI_DONTVALIDATEPATH OR WIXUI_INSTALLDIR_VALID="1" - 1 - 1 - 1 - - NOT Installed - Installed AND NOT PATCH - Installed AND PATCH - - 1 - - 1 - 1 - 1 - - - - - - - diff --git a/assets/wix/WixUIBannerBmp.png b/assets/wix/WixUIBannerBmp.png deleted file mode 100644 index 2a1922c1d7c..00000000000 Binary files a/assets/wix/WixUIBannerBmp.png and /dev/null differ diff --git a/assets/wix/WixUIDialogBmp.png b/assets/wix/WixUIDialogBmp.png deleted file mode 100644 index 57cf3734c17..00000000000 Binary files a/assets/wix/WixUIDialogBmp.png and /dev/null differ diff --git a/assets/wix/WixUIInfoIco.png b/assets/wix/WixUIInfoIco.png deleted file mode 100644 index cb705d5f33f..00000000000 Binary files a/assets/wix/WixUIInfoIco.png and /dev/null differ diff --git a/assets/wix/bundle.wxs b/assets/wix/bundle.wxs deleted file mode 100644 index a2d93b47099..00000000000 --- a/assets/wix/bundle.wxs +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/build.psm1 b/build.psm1 index 34d79670fb4..d353eb251e5 100644 --- a/build.psm1 +++ b/build.psm1 @@ -35,6 +35,16 @@ $tagsUpToDate = $false # This function is used during the setup phase in tools/ci.psm1 function Sync-PSTags { + <# + .SYNOPSIS + Syncs git tags from the PowerShell/PowerShell upstream remote. + .DESCRIPTION + Ensures that tags from the PowerShell/PowerShell upstream remote have been fetched. + Functions like Get-PSCommitId and Get-PSLatestTag require tags to be current. + This is called during the setup phase in tools/ci.psm1. + .PARAMETER AddRemoteIfMissing + If specified, adds the upstream remote automatically when it is not present. + #> param( [Switch] $AddRemoteIfMissing @@ -78,6 +88,15 @@ function Sync-PSTags # Gets the latest tag for the current branch function Get-PSLatestTag { + <# + .SYNOPSIS + Gets the latest git tag reachable from the current HEAD. + .DESCRIPTION + Returns the most recent annotated git tag. Run Sync-PSTags first to ensure tags + are up to date; otherwise a warning is emitted. + .OUTPUTS + System.String. The latest tag string, e.g. 'v7.5.0'. + #> [CmdletBinding()] param() # This function won't always return the correct value unless tags have been sync'ed @@ -92,6 +111,17 @@ function Get-PSLatestTag function Get-PSVersion { + <# + .SYNOPSIS + Returns the PowerShell version string for the current commit. + .DESCRIPTION + Derives the version from the latest git tag, optionally omitting the commit-ID suffix. + .PARAMETER OmitCommitId + When specified, returns only the bare version (e.g. '7.5.0') from the latest tag, + without the commit-count and hash suffix appended by git describe. + .OUTPUTS + System.String. A version string such as '7.5.0' or '7.5.0-15-gabcdef1234'. + #> [CmdletBinding()] param( [switch] @@ -109,6 +139,16 @@ function Get-PSVersion function Get-PSCommitId { + <# + .SYNOPSIS + Returns the PowerShell commit-ID string produced by git describe. + .DESCRIPTION + Returns the full git describe string including the tag, number of commits since + the tag, and the abbreviated commit hash (e.g. 'v7.5.0-15-gabcdef1234567890'). + Run Sync-PSTags first; otherwise a warning is emitted. + .OUTPUTS + System.String. A git describe string such as 'v7.5.0-15-gabcdef1234567890'. + #> [CmdletBinding()] param() # This function won't always return the correct value unless tags have been sync'ed @@ -123,6 +163,19 @@ function Get-PSCommitId function Get-EnvironmentInformation { + <# + .SYNOPSIS + Collects information about the current operating environment. + .DESCRIPTION + Returns a PSCustomObject containing OS-identity flags, architecture, admin status, + NuGet package root paths, and Linux distribution details. The object is used + throughout the build module to make platform-conditional decisions. + .OUTPUTS + System.Management.Automation.PSCustomObject. An object with properties such as + IsWindows, IsLinux, IsMacOS, IsAdmin, OSArchitecture, and distribution-specific flags + (IsUbuntu, IsDebian, IsRedHatFamily, etc.). + #> + param() $environment = @{'IsWindows' = [System.Environment]::OSVersion.Platform -eq [System.PlatformID]::Win32NT} # PowerShell will likely not be built on pre-1709 nanoserver if ('System.Management.Automation.Platform' -as [type]) { @@ -193,7 +246,7 @@ function Get-EnvironmentInformation $environment += @{'IsRedHatFamily' = $environment.IsCentOS -or $environment.IsFedora -or $environment.IsRedHat} $environment += @{'IsSUSEFamily' = $environment.IsSLES -or $environment.IsOpenSUSE} $environment += @{'IsAlpine' = $LinuxInfo.ID -match 'alpine'} - $environment += @{'IsMariner' = $LinuxInfo.ID -match 'mariner'} + $environment += @{'IsMariner' = $LinuxInfo.ID -match 'mariner' -or $LinuxInfo.ID -match 'azurelinux'} # Workaround for temporary LD_LIBRARY_PATH hack for Fedora 24 # https://github.com/PowerShell/PowerShell/issues/2511 @@ -281,6 +334,54 @@ function Test-IsReleaseCandidate $optimizedFddRegex = 'fxdependent-(linux|win|win7|osx)-(x64|x86|arm64|arm)' function Start-PSBuild { + <# + .SYNOPSIS + Builds PowerShell from source using dotnet publish. + .DESCRIPTION + Compiles the PowerShell source tree for the specified runtime and configuration. + Optionally restores NuGet packages, regenerates resources, generates the type catalog, + and restores Gallery modules. Saves build options so subsequent commands can reuse them. + .PARAMETER StopDevPowerShell + Stops any running dev pwsh process before building to prevent file-in-use errors. + .PARAMETER Restore + Forces NuGet package restore even when packages already exist. + .PARAMETER Output + Path to the output directory. Defaults to the standard build location. + .PARAMETER ResGen + Regenerates C# bindings for resx resource files before building. + .PARAMETER TypeGen + Regenerates the CorePsTypeCatalog.cs type-catalog file before building. + .PARAMETER Clean + Runs 'git clean -fdX' to remove untracked and ignored files before building. + .PARAMETER PSModuleRestore + Restores PowerShell Gallery modules to the build output directory (legacy parameter set). + .PARAMETER NoPSModuleRestore + Skips restoring PowerShell Gallery modules to the build output directory. + .PARAMETER CI + Indicates a CI build; restores the Pester module to the output directory. + .PARAMETER ForMinimalSize + Produces a build optimized for minimal binary size (linux-x64, win7-x64, or osx-x64 only). + .PARAMETER SkipExperimentalFeatureGeneration + Skips the step that runs the built pwsh to produce the experimental-features list. + .PARAMETER SMAOnly + Rebuilds only System.Management.Automation.dll for rapid engine iteration. + .PARAMETER UseNuGetOrg + Uses nuget.org instead of the private PowerShell feed for package restore. + .PARAMETER Runtime + The .NET runtime identifier (RID) to target, e.g. 'win7-x64' or 'linux-x64'. + .PARAMETER Configuration + The build configuration: Debug, Release, CodeCoverage, or StaticAnalysis. + .PARAMETER ReleaseTag + A git tag in 'vX.Y.Z[-preview.N|-rc.N]' format to embed as the release version. + .PARAMETER Detailed + Passes '--verbosity d' to dotnet for detailed build output. + .PARAMETER InteractiveAuth + Passes '--interactive' to dotnet restore for interactive feed authentication. + .PARAMETER SkipRoslynAnalyzers + Skips Roslyn analyzer execution during the build. + .PARAMETER PSOptionsPath + When supplied, saves the resolved build options to this JSON file path. + #> [CmdletBinding(DefaultParameterSetName="Default")] param( # When specified this switch will stops running dev powershell @@ -353,8 +454,8 @@ function Start-PSBuild { $PSModuleRestore = $true } - if ($Runtime -eq "linux-arm" -and $environment.IsLinux -and -not $environment.IsUbuntu) { - throw "Cross compiling for linux-arm is only supported on Ubuntu environment" + if ($Runtime -eq "linux-arm" -and $environment.IsLinux -and -not $environment.IsUbuntu -and -not $environment.IsMariner) { + throw "Cross compiling for linux-arm is only supported on AzureLinux/Ubuntu environment" } if ("win-arm","win-arm64" -contains $Runtime -and -not $environment.IsWindows) { @@ -385,7 +486,7 @@ function Start-PSBuild { } if ($Clean) { - Write-Log -message "Cleaning your working directory. You can also do it with 'git clean -fdX --exclude .vs/PowerShell/v16/Server/sqlite3'" + Write-LogGroupStart -Title "Cleaning your working directory" Push-Location $PSScriptRoot try { # Excluded sqlite3 folder is due to this Roslyn issue: https://github.com/dotnet/roslyn/issues/23060 @@ -393,6 +494,7 @@ function Start-PSBuild { # Excluded nuget.config as this is required for release build. git clean -fdX --exclude .vs/PowerShell/v16/Server/sqlite3 --exclude src/Modules/nuget.config --exclude nuget.config } finally { + Write-LogGroupEnd -Title "Cleaning your working directory" Pop-Location } } @@ -492,13 +594,24 @@ Fix steps: $Arguments += "/property:IsWindows=false" } - # Framework Dependent builds do not support ReadyToRun as it needs a specific runtime to optimize for. - # The property is set in Powershell.Common.props file. - # We override the property through the build command line. - if(($Options.Runtime -like 'fxdependent*' -or $ForMinimalSize) -and $Options.Runtime -notmatch $optimizedFddRegex) { - $Arguments += "/property:PublishReadyToRun=false" + # We pass in the AppDeployment property to indicate which type of deployment we are doing. + # This allows the PowerShell.Common.props to set the correct properties for the build. + $AppDeployment = if(($Options.Runtime -like 'fxdependent*' -or $ForMinimalSize) -and $Options.Runtime -notmatch $optimizedFddRegex) { + # Global and zip files + "FxDependent" + } + elseif($Options.Runtime -like 'fxdependent*' -and $Options.Runtime -match $optimizedFddRegex) { + # These are Optimized and must come from the correct version of the runtime. + # Global + "FxDependentDeployment" + } + else { + # The majority of our packages + # AppLocal + "SelfContained" } + $Arguments += "/property:AppDeployment=$AppDeployment" $Arguments += "--configuration", $Options.Configuration $Arguments += "--framework", $Options.Framework @@ -525,7 +638,9 @@ Fix steps: } # handle Restore + Write-LogGroupStart -Title "Restore NuGet Packages" Restore-PSPackage -Options $Options -Force:$Restore -InteractiveAuth:$InteractiveAuth + Write-LogGroupEnd -Title "Restore NuGet Packages" # handle ResGen # Heuristic to run ResGen on the fresh machine @@ -555,6 +670,7 @@ Fix steps: $publishPath = $Options.Output } + Write-LogGroupStart -Title "Build PowerShell" try { # Relative paths do not work well if cwd is not changed to project Push-Location $Options.Top @@ -609,6 +725,7 @@ Fix steps: } finally { Pop-Location } + Write-LogGroupEnd -Title "Build PowerShell" # No extra post-building task will run if '-SMAOnly' is specified, because its purpose is for a quick update of S.M.A.dll after full build. if ($SMAOnly) { @@ -616,6 +733,7 @@ Fix steps: } # publish reference assemblies + Write-LogGroupStart -Title "Publish Reference Assemblies" try { Push-Location "$PSScriptRoot/src/TypeCatalogGen" $refAssemblies = Get-Content -Path $incFileName | Where-Object { $_ -like "*microsoft.netcore.app*" } | ForEach-Object { $_.TrimEnd(';') } @@ -629,6 +747,7 @@ Fix steps: } finally { Pop-Location } + Write-LogGroupEnd -Title "Publish Reference Assemblies" if ($ReleaseTag) { $psVersion = $ReleaseTag @@ -671,10 +790,13 @@ Fix steps: # download modules from powershell gallery. # - PowerShellGet, PackageManagement, Microsoft.PowerShell.Archive if ($PSModuleRestore) { + Write-LogGroupStart -Title "Restore PowerShell Modules" Restore-PSModuleToBuild -PublishPath $publishPath + Write-LogGroupEnd -Title "Restore PowerShell Modules" } # publish powershell.config.json + Write-LogGroupStart -Title "Generate PowerShell Configuration" $config = [ordered]@{} if ($Options.Runtime -like "*win*") { @@ -720,10 +842,13 @@ Fix steps: } else { Write-Warning "No powershell.config.json generated for $publishPath" } + Write-LogGroupEnd -Title "Generate PowerShell Configuration" # Restore the Pester module if ($CI) { + Write-LogGroupStart -Title "Restore Pester Module" Restore-PSPester -Destination (Join-Path $publishPath "Modules") + Write-LogGroupEnd -Title "Restore Pester Module" } Clear-NativeDependencies -PublishFolder $publishPath @@ -739,6 +864,20 @@ Fix steps: } function Switch-PSNugetConfig { + <# + .SYNOPSIS + Switches the NuGet configuration between public, private, and NuGet.org-only sources. + .DESCRIPTION + Regenerates nuget.config files in the repository root, src/Modules, and test/tools/Modules + to point to the specified feed source. Optionally stores authenticated credentials. + .PARAMETER Source + The feed set to activate: 'Public' (nuget.org + dotnet feed), 'Private' (PowerShell ADO + feed), or 'NuGetOnly' (nuget.org only). + .PARAMETER UserName + Username for authenticated private feed access. + .PARAMETER ClearTextPAT + Personal access token in clear text for authenticated private feed access. + #> param( [Parameter(Mandatory = $true, ParameterSetName = 'user')] [Parameter(Mandatory = $true, ParameterSetName = 'nouser')] @@ -790,6 +929,18 @@ function Switch-PSNugetConfig { function Test-ShouldGenerateExperimentalFeatures { + <# + .SYNOPSIS + Determines whether experimental-feature JSON files should be generated on this host. + .DESCRIPTION + Returns $true only when the current runtime identifier matches the host OS and + architecture, the build is not a release build (PS_RELEASE_BUILD not set), and the + runtime is not fxdependent. + .PARAMETER Runtime + The .NET runtime identifier (RID) being targeted by the build. + .OUTPUTS + System.Boolean. $true if the experimental-feature list should be generated. + #> param( [Parameter(Mandatory)] $Runtime @@ -829,6 +980,23 @@ function Test-ShouldGenerateExperimentalFeatures function Restore-PSPackage { + <# + .SYNOPSIS + Restores NuGet packages for the PowerShell project directories. + .DESCRIPTION + Runs 'dotnet restore' on the main PowerShell project directories with up to five + retries on transient failures. Honors the target runtime identifier and build verbosity. + .PARAMETER ProjectDirs + Explicit list of project directories to restore. Defaults to the standard PS project set. + .PARAMETER Options + PSOptions object specifying runtime and configuration. Defaults to Get-PSOptions. + .PARAMETER Force + Forces restore even when project.assets.json already exists. + .PARAMETER InteractiveAuth + Passes '--interactive' to dotnet restore for interactive feed authentication. + .PARAMETER PSModule + Restores in PSModule mode, omitting the runtime argument. + #> [CmdletBinding()] param( [ValidateNotNullOrEmpty()] @@ -943,6 +1111,16 @@ function Restore-PSPackage function Restore-PSModuleToBuild { + <# + .SYNOPSIS + Copies PowerShell Gallery modules from the NuGet cache into the build output Modules folder. + .DESCRIPTION + Resolves Gallery module packages referenced in PSGalleryModules.csproj and copies + them to the Modules subdirectory of the specified publish path. Also removes + .nupkg.metadata files left behind by the restore. + .PARAMETER PublishPath + The PowerShell build output directory whose Modules sub-folder receives the modules. + #> param( [Parameter(Mandatory)] [string] @@ -959,6 +1137,14 @@ function Restore-PSModuleToBuild function Restore-PSPester { + <# + .SYNOPSIS + Downloads and saves the Pester module (v4.x) from the PowerShell Gallery. + .DESCRIPTION + Uses Save-Module to install Pester up to version 4.99 into the target directory. + .PARAMETER Destination + Directory to save Pester into. Defaults to the Modules folder of the current build output. + #> param( [ValidateNotNullOrEmpty()] [string] $Destination = ([IO.Path]::Combine((Split-Path (Get-PSOptions -DefaultToNew).Output), "Modules")) @@ -967,6 +1153,15 @@ function Restore-PSPester } function Compress-TestContent { + <# + .SYNOPSIS + Compresses the test directory into a zip archive for distribution. + .DESCRIPTION + Publishes PSTestTools and then zips the entire test/ directory to the given + destination path using System.IO.Compression.ZipFile. + .PARAMETER Destination + The path of the output zip file to create. + #> [CmdletBinding()] param( $Destination @@ -981,13 +1176,37 @@ function Compress-TestContent { } function New-PSOptions { + <# + .SYNOPSIS + Creates a new PSOptions hashtable describing a PowerShell build configuration. + .DESCRIPTION + Computes the output path, project directory, and framework for a PowerShell build + based on the supplied runtime and configuration. The resulting hashtable is consumed + by Start-PSBuild, Restore-PSPackage, and related functions. + .PARAMETER Configuration + The build configuration: Debug (default), Release, CodeCoverage, or StaticAnalysis. + .PARAMETER Framework + The target .NET framework moniker. Defaults to 'net11.0'. + .PARAMETER Runtime + The .NET runtime identifier (RID). Detected automatically via 'dotnet --info' if omitted. + .PARAMETER Output + Optional path to the output directory. The executable name is appended automatically. + .PARAMETER SMAOnly + Targets only the System.Management.Automation project rather than the full host. + .PARAMETER PSModuleRestore + Indicates whether Start-PSBuild should restore PowerShell Gallery modules. + .PARAMETER ForMinimalSize + Produces a build targeting minimal binary size. + .OUTPUTS + System.Collections.Hashtable. A hashtable with build option properties. + #> [CmdletBinding()] param( [ValidateSet('Debug', 'Release', 'CodeCoverage', 'StaticAnalysis', '')] [string]$Configuration, - [ValidateSet("net10.0")] - [string]$Framework = "net10.0", + [ValidateSet("net11.0")] + [string]$Framework = "net11.0", # These are duplicated from Start-PSBuild # We do not use ValidateScript since we want tab completion @@ -1128,6 +1347,17 @@ function New-PSOptions { # Get the Options of the last build function Get-PSOptions { + <# + .SYNOPSIS + Returns the PSOptions from the most recent Start-PSBuild call. + .DESCRIPTION + Retrieves the script-level $script:Options object. If no build has been run and + -DefaultToNew is specified, returns a fresh object from New-PSOptions. + .PARAMETER DefaultToNew + When specified, returns default options from New-PSOptions if no build has occurred. + .OUTPUTS + System.Collections.Hashtable. The current PSOptions hashtable, or $null. + #> param( [Parameter(HelpMessage='Defaults to New-PSOption if a build has not occurred.')] [switch] @@ -1143,6 +1373,15 @@ function Get-PSOptions { } function Set-PSOptions { + <# + .SYNOPSIS + Stores the supplied PSOptions as the active build options. + .DESCRIPTION + Writes the options hashtable to the script-scoped $script:Options variable, + making it available to subsequent Get-PSOptions calls. + .PARAMETER Options + The PSOptions hashtable to store. + #> param( [PSObject] $Options @@ -1152,6 +1391,17 @@ function Set-PSOptions { } function Get-PSOutput { + <# + .SYNOPSIS + Returns the path to the PowerShell executable produced by the build. + .DESCRIPTION + Looks up the Output path from the supplied options hashtable, the cached + script-level options, or a fresh New-PSOptions call, in that order of precedence. + .PARAMETER Options + An explicit options hashtable. If omitted, the most recent build options are used. + .OUTPUTS + System.String. The full path to the built pwsh or pwsh.exe executable. + #> [CmdletBinding()]param( [hashtable]$Options ) @@ -1165,6 +1415,21 @@ function Get-PSOutput { } function Get-PesterTag { + <# + .SYNOPSIS + Scans the Pester test tree and returns a summary of all tags in use. + .DESCRIPTION + Parses every *.tests.ps1 file under the specified base directory using the + PowerShell AST, validates that each Describe block has exactly one priority tag + (CI, Feature, or Scenario), and returns a summary object with tag counts and + any validation warnings. + .PARAMETER testbase + Root directory to search for test files. + Defaults to '$PSScriptRoot/test/powershell'. + .OUTPUTS + PSCustomObject (DescribeTagsInUse). Properties are tag names mapped to usage + counts, plus 'Result' (Pass/Fail) and 'Warnings' (string[]). + #> param ( [Parameter(Position=0)][string]$testbase = "$PSScriptRoot/test/powershell" ) $alltags = @{} $warnings = @() @@ -1231,6 +1496,13 @@ function Get-PesterTag { # testing PowerShell remote custom connections. function Publish-CustomConnectionTestModule { + <# + .SYNOPSIS + Builds and publishes the Microsoft.PowerShell.NamedPipeConnection test module. + .DESCRIPTION + Invokes the module's own build.ps1 script, copies the output to + test/tools/Modules, and then runs a clean build to remove intermediate artifacts. + #> Write-LogGroupStart -Title "Publish-CustomConnectionTestModule" $sourcePath = "${PSScriptRoot}/test/tools/NamedPipeConnection" $outPath = "${PSScriptRoot}/test/tools/NamedPipeConnection/out/Microsoft.PowerShell.NamedPipeConnection" @@ -1261,6 +1533,18 @@ function Publish-CustomConnectionTestModule } function Publish-PSTestTools { + <# + .SYNOPSIS + Builds and publishes all test tool projects to their bin directories. + .DESCRIPTION + Runs 'dotnet publish' for each test tool project (TestAlc, TestExe, UnixSocket, + WebListener, and on Windows TestService), copies Gallery test modules, and + publishes the NamedPipeConnection module. The tool bin directories are added to PATH + so that tests can locate the executables. + .PARAMETER runtime + The .NET runtime identifier (RID) used when publishing executables. + Defaults to the runtime from the current build options. + #> [CmdletBinding()] param( [string] @@ -1343,6 +1627,16 @@ function Publish-PSTestTools { } function Get-ExperimentalFeatureTests { + <# + .SYNOPSIS + Returns a mapping of experimental feature names to their associated test files. + .DESCRIPTION + Reads test/tools/TestMetadata.json and extracts the ExperimentalFeatures section, + returning a hashtable where keys are feature names and values are arrays of test paths. + .OUTPUTS + System.Collections.Hashtable. Keys are experimental feature names; values are + arrays of test file paths. + #> $testMetadataFile = Join-Path $PSScriptRoot "test/tools/TestMetadata.json" $metadata = Get-Content -Path $testMetadataFile -Raw | ConvertFrom-Json | ForEach-Object -MemberName ExperimentalFeatures $features = $metadata | Get-Member -MemberType NoteProperty | ForEach-Object -MemberName Name @@ -1355,6 +1649,57 @@ function Get-ExperimentalFeatureTests { } function Start-PSPester { + <# + .SYNOPSIS + Runs the Pester test suite against the built PowerShell. + .DESCRIPTION + Launches the built pwsh process with the Pester module and runs the specified + test paths. Automatically adjusts tag exclusions based on the current elevation + level, and emits NUnit XML results that are optionally published to Azure DevOps + or GitHub Actions. + .PARAMETER Path + One or more test file or directory paths to run. Defaults to test/powershell. + .PARAMETER OutputFormat + The Pester output format. Defaults to 'NUnitXml'. + .PARAMETER OutputFile + Path for the XML results file. Defaults to 'pester-tests.xml'. + .PARAMETER ExcludeTag + Tags to exclude from the run. Defaults to 'Slow'; adjusted for elevation level. + .PARAMETER Tag + Tags to include in the run. Defaults to 'CI' and 'Feature'. + .PARAMETER ThrowOnFailure + Throws an exception after the run if any tests failed. + .PARAMETER BinDir + Directory containing the built pwsh executable. Defaults to the current build output. + .PARAMETER powershell + Full path to the pwsh executable used for running tests. + .PARAMETER Pester + Path to the Pester module directory. + .PARAMETER Unelevate + Runs tests in an unelevated child process on Windows. + .PARAMETER Quiet + Suppresses most Pester output. + .PARAMETER Terse + Shows compact pass/fail indicators instead of full output lines. + .PARAMETER PassThru + Returns the Pester result object to the caller. + .PARAMETER Sudo + Runs tests under sudo on Unix (PassThru parameter set). + .PARAMETER IncludeFailingTest + Includes tests from tools/failingTests. + .PARAMETER IncludeCommonTests + Includes tests from test/common. + .PARAMETER ExperimentalFeatureName + Enables the named experimental feature for this test run via a temporary config file. + .PARAMETER Title + Title for the published test results. Defaults to 'PowerShell 7 Tests'. + .PARAMETER Wait + Waits for a debugger to attach before starting Pester (Debug builds only). + .PARAMETER SkipTestToolBuild + Skips rebuilding test tool executables before running tests. + .PARAMETER UseNuGetOrg + Switches NuGet config to public feeds before running tests. + #> [CmdletBinding(DefaultParameterSetName='default')] param( [Parameter(Position=0)] @@ -1404,7 +1749,7 @@ function Start-PSPester { if($IncludeCommonTests.IsPresent) { - $path = += "$PSScriptRoot/test/common" + $path += "$PSScriptRoot/test/common" } # we need to do few checks and if user didn't provide $ExcludeTag explicitly, we should alternate the default @@ -1720,6 +2065,20 @@ function Start-PSPester { function Publish-TestResults { + <# + .SYNOPSIS + Publishes test result files to Azure DevOps or GitHub Actions. + .DESCRIPTION + In an Azure DevOps build (TF_BUILD), uploads the result file via a ##vso command + and attaches it as a build artifact. In GitHub Actions, copies the file to the + testResults directory under $env:RUNNER_WORKSPACE. Does nothing outside of CI environments. + .PARAMETER Title + The run title shown in the CI testing tab. + .PARAMETER Path + Path to the NUnit or XUnit result file to publish. + .PARAMETER Type + The result file format: 'NUnit' (default) or 'XUnit'. + #> param( [Parameter(Mandatory)] [string] @@ -1780,6 +2139,17 @@ function Publish-TestResults function script:Start-UnelevatedProcess { + <# + .SYNOPSIS + Starts a process at an unelevated trust level on Windows. + .DESCRIPTION + Uses runas.exe /trustlevel:0x20000 to launch a process without elevation. + Only supported on Windows and non-arm64 architectures. + .PARAMETER process + The path to the executable to start. + .PARAMETER arguments + Arguments to pass to the executable. + #> param( [string]$process, [string[]]$arguments @@ -1790,7 +2160,7 @@ function script:Start-UnelevatedProcess throw "Start-UnelevatedProcess is currently not supported on non-Windows platforms" } - if (-not $environment.OSArchitecture -eq 'arm64') + if ($environment.OSArchitecture -eq 'arm64') { throw "Start-UnelevatedProcess is currently not supported on arm64 platforms" } @@ -1800,6 +2170,18 @@ function script:Start-UnelevatedProcess function Show-PSPesterError { + <# + .SYNOPSIS + Outputs a formatted error block for a single Pester test failure. + .DESCRIPTION + Accepts either an XmlElement from a NUnit result file or a PSCustomObject from + a Pester PassThru result, and writes a structured description/name/message/stack-trace + block to the log output. + .PARAMETER testFailure + An XML test-case element from a Pester NUnit result file (xml parameter set). + .PARAMETER testFailureObject + A Pester test-result PSCustomObject from a PassThru run (object parameter set). + #> [CmdletBinding(DefaultParameterSetName='xml')] param ( [Parameter(ParameterSetName='xml',Mandatory)] @@ -1840,8 +2222,92 @@ $stack_trace } +function Get-PesterFailureFileInfo +{ + <# + .SYNOPSIS + Parses a Pester stack-trace string and returns the source file path and line number. + .DESCRIPTION + Tries several common stack-trace formats produced by Pester 4 and Pester 5 (on + both Windows and Unix) and returns a hashtable with File and Line keys. + Returns $null values for both keys when no pattern matches. + .PARAMETER StackTraceString + The raw stack trace text from a Pester test failure. + .OUTPUTS + System.Collections.Hashtable. A hashtable with 'File' (string) and 'Line' (string). + #> + [CmdletBinding()] + param ( + [Parameter(Mandatory)] + [string]$StackTraceString + ) + + # Parse stack trace to extract file path and line number + # Common patterns: + # "at line: 123 in C:\path\to\file.ps1" (Pester 4) + # "at C:\path\to\file.ps1:123" + # "at , C:\path\to\file.ps1: line 123" + # "at 1 | Should -Be 2, /path/to/file.ps1:123" (Pester 5) + # "at 1 | Should -Be 2, C:\path\to\file.ps1:123" (Pester 5 Windows) + + $result = @{ + File = $null + Line = $null + } + + if ([string]::IsNullOrWhiteSpace($StackTraceString)) { + return $result + } + + # Try pattern: "at line: 123 in " (Pester 4) + if ($StackTraceString -match 'at line:\s*(\d+)\s+in\s+(.+?)(?:\r|\n|$)') { + $result.Line = $matches[1] + $result.File = $matches[2].Trim() + return $result + } + + # Try pattern: ", :123" (Pester 5 format) + # This handles both Unix paths (/path/file.ps1:123) and Windows paths (C:\path\file.ps1:123) + if ($StackTraceString -match ',\s*((?:[A-Za-z]:)?[\/\\].+?\.ps[m]?1):(\d+)') { + $result.File = $matches[1].Trim() + $result.Line = $matches[2] + return $result + } + + # Try pattern: "at :123" (without comma) + # Handle both absolute Unix and Windows paths + if ($StackTraceString -match 'at\s+((?:[A-Za-z]:)?[\/\\][^,]+?\.ps[m]?1):(\d+)(?:\r|\n|$)') { + $result.File = $matches[1].Trim() + $result.Line = $matches[2] + return $result + } + + # Try pattern: ": line 123" + if ($StackTraceString -match '((?:[A-Za-z]:)?[\/\\][^,]+?\.ps[m]?1):\s*line\s+(\d+)(?:\r|\n|$)') { + $result.File = $matches[1].Trim() + $result.Line = $matches[2] + return $result + } + + # Try to extract just the file path if no line number found + if ($StackTraceString -match '(?:at\s+|in\s+)?((?:[A-Za-z]:)?[\/\\].+?\.ps[m]?1)') { + $result.File = $matches[1].Trim() + } + + return $result +} + function Test-XUnitTestResults { + <# + .SYNOPSIS + Validates an xUnit XML result file and throws if any tests failed. + .DESCRIPTION + Parses the specified xUnit result file, logs description, name, message, and + stack trace for each failed test, then throws an exception summarizing the count. + .PARAMETER TestResultsFile + Path to the xUnit XML result file to validate. + #> param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] @@ -1897,6 +2363,23 @@ function Test-XUnitTestResults # Throw if a test failed function Test-PSPesterResults { + <# + .SYNOPSIS + Validates Pester test results and throws if any tests failed. + .DESCRIPTION + In file mode, reads a NUnit XML result file and logs each failure before throwing. + In object mode, inspects a Pester PassThru result object. Optionally permits + empty result sets. + .PARAMETER TestResultsFile + Path to the NUnit XML result file. Defaults to 'pester-tests.xml'. + .PARAMETER TestArea + Label for the test area, used in error messages. Defaults to 'test/powershell'. + .PARAMETER ResultObject + A Pester PassThru result object to inspect instead of parsing a file. + .PARAMETER CanHaveNoResult + When specified with ResultObject, suppresses the 'NO TESTS RUN' exception for + zero-count results. + #> [CmdletBinding(DefaultParameterSetName='file')] param( [Parameter(ParameterSetName='file')] @@ -1972,8 +2455,24 @@ function Test-PSPesterResults } function Start-PSxUnit { + <# + .SYNOPSIS + Runs the xUnit tests for the PowerShell engine. + .DESCRIPTION + Executes 'dotnet test' in the test/xUnit directory against the built PowerShell + binaries. On Unix, copies native libraries and required dependencies into the test + output directory. Publishes results to CI when not in debug-logging mode. + .PARAMETER xUnitTestResultsFile + Path for the xUnit XML result file. Defaults to 'xUnitResults.xml'. + .PARAMETER DebugLogging + Enables detailed console test output instead of writing an XML result file. + .PARAMETER Filter + An xUnit filter expression to restrict which tests are run. + #> [CmdletBinding()]param( - [string] $xUnitTestResultsFile = "xUnitResults.xml" + [string] $xUnitTestResultsFile = "xUnitResults.xml", + [switch] $DebugLogging, + [string] $Filter ) # Add .NET CLI tools to PATH @@ -2031,9 +2530,28 @@ function Start-PSxUnit { # We run the xUnit tests sequentially to avoid race conditions caused by manipulating the config.json file. # xUnit tests run in parallel by default. To make them run sequentially, we need to define the 'xunit.runner.json' file. - dotnet test --configuration $Options.configuration --test-adapter-path:. "--logger:xunit;LogFilePath=$xUnitTestResultsFile" + $extraParams = @() + if($Filter) { + $extraParams += @( + '--filter' + $Filter + ) + } + + if($DebugLogging) { + $extraParams += @( + "--logger:console;verbosity=detailed" + ) + } else { + $extraParams += @( + "--logger:xunit;LogFilePath=$xUnitTestResultsFile" + ) + } + dotnet test @extraParams --configuration $Options.configuration --test-adapter-path:. - Publish-TestResults -Path $xUnitTestResultsFile -Type 'XUnit' -Title 'Xunit Sequential' + if(!$DebugLogging){ + Publish-TestResults -Path $xUnitTestResultsFile -Type 'XUnit' -Title 'Xunit Sequential' + } } finally { $env:DOTNET_ROOT = $originalDOTNET_ROOT @@ -2042,6 +2560,29 @@ function Start-PSxUnit { } function Install-Dotnet { + <# + .SYNOPSIS + Installs the .NET SDK using the official install script. + .DESCRIPTION + Downloads and runs dotnet-install.sh (Linux/macOS) or dotnet-install.ps1 (Windows) + to install the specified SDK version into the user-local dotnet installation directory. + .PARAMETER Channel + The release channel to install from when no explicit version is given. + .PARAMETER Version + The exact SDK version to install. Defaults to the version required by this repository. + .PARAMETER Quality + The quality level (e.g. 'GA', 'preview') used when installing by channel. + .PARAMETER RemovePreviousVersion + Attempts to uninstall previously installed dotnet packages before installing. + .PARAMETER NoSudo + Omits sudo from install commands, useful inside containers running as root. + .PARAMETER InstallDir + Custom installation directory for the .NET SDK. + .PARAMETER AzureFeed + Override URL for the Azure CDN feed used to download the SDK. + .PARAMETER FeedCredential + Credential token for accessing a private Azure feed. + #> [CmdletBinding()] param( [string]$Channel = $dotnetCLIChannel, @@ -2054,14 +2595,15 @@ function Install-Dotnet { [string]$FeedCredential ) + Write-LogGroupStart -Title "Install .NET SDK $Version" Write-Verbose -Verbose "In install-dotnet" # This allows sudo install to be optional; needed when running in containers / as root # Note that when it is null, Invoke-Expression (but not &) must be used to interpolate properly $sudo = if (!$NoSudo) { "sudo" } - # $installObtainUrl = "https://dot.net/v1" - $installObtainUrl = "https://dotnet.microsoft.com/download/dotnet/scripts/v1" + $installObtainUrl = "https://builds.dotnet.microsoft.com/dotnet/scripts/v1" + #$installObtainUrl = "https://dotnet.microsoft.com/download/dotnet/scripts/v1" $uninstallObtainUrl = "https://raw.githubusercontent.com/dotnet/cli/master/scripts/obtain" # Install for Linux and OS X @@ -2152,7 +2694,6 @@ function Install-Dotnet { $installArgs += @{ SkipNonVersionedFiles = $true } $installArgs | Out-String | Write-Verbose -Verbose - & ./$installScript @installArgs } else { @@ -2189,56 +2730,52 @@ function Install-Dotnet { } } } + Write-LogGroupEnd -Title "Install .NET SDK $Version" } function Get-RedHatPackageManager { + <# + .SYNOPSIS + Returns the install command prefix for the available Red Hat-family package manager. + .DESCRIPTION + Detects whether yum, dnf, or tdnf is installed and returns the corresponding + install command string for use in bootstrapping scripts. + .OUTPUTS + System.String. A package-manager install command such as 'dnf install -y -q'. + #> if ($environment.IsCentOS -or (Get-Command -Name yum -CommandType Application -ErrorAction SilentlyContinue)) { "yum install -y -q" } elseif ($environment.IsFedora -or (Get-Command -Name dnf -CommandType Application -ErrorAction SilentlyContinue)) { "dnf install -y -q" + } elseif ($environment.IsMariner -or (Get-Command -Name Test-DscConfiguration -CommandType Application -ErrorAction SilentlyContinue)) { + "tdnf install -y -q" } else { throw "Error determining package manager for this distribution." } } -function Install-GlobalGem { - param( - [Parameter()] - [string] - $Sudo = "", - - [Parameter(Mandatory)] - [string] - $GemName, - - [Parameter(Mandatory)] - [string] - $GemVersion - ) - try { - # We cannot guess if the user wants to run gem install as root on linux and windows, - # but macOs usually requires sudo - $gemsudo = '' - if($environment.IsMacOS -or $env:TF_BUILD) { - $gemsudo = $sudo - } - - Start-NativeExecution ([ScriptBlock]::Create("$gemsudo gem install $GemName -v $GemVersion --no-document")) - - } catch { - Write-Warning "Installation of gem $GemName $GemVersion failed! Must resolve manually." - $logs = Get-ChildItem "/var/lib/gems/*/extensions/x86_64-linux/*/$GemName-*/gem_make.out" | Select-Object -ExpandProperty FullName - foreach ($log in $logs) { - Write-Verbose "Contents of: $log" -Verbose - Get-Content -Raw -Path $log -ErrorAction Ignore | ForEach-Object { Write-Verbose $_ -Verbose } - Write-Verbose "END Contents of: $log" -Verbose - } - - throw - } -} - function Start-PSBootstrap { + <# + .SYNOPSIS + Installs build dependencies for PowerShell. + .DESCRIPTION + Depending on the selected scenario, installs native OS packages, the required + .NET SDK, Windows packaging tools (WiX), and/or .NET global tools (dotnet-format). + Supports Linux, macOS, and Windows. + .PARAMETER Channel + The .NET SDK release channel to use when installing by channel. + .PARAMETER Version + The exact .NET SDK version to install. Defaults to the required version. + .PARAMETER NoSudo + Omits sudo from native-package install commands, useful inside containers. + .PARAMETER BuildLinuxArm + Installs Linux ARM cross-compilation dependencies (Ubuntu/AzureLinux only). + .PARAMETER Force + Forces .NET SDK reinstallation even if the correct version is already present. + .PARAMETER Scenario + What to install: 'Package' (packaging tools), 'DotNet' (.NET SDK), + 'Both' (Package + DotNet), 'Tools' (.NET global tools), or 'All' (everything). + #> [CmdletBinding()] param( [string]$Channel = $dotnetCLIChannel, @@ -2249,7 +2786,12 @@ function Start-PSBootstrap { [switch]$BuildLinuxArm, [switch]$Force, [Parameter(Mandatory = $true)] - [ValidateSet("Package", "DotNet", "Both")] + # Package: Install dependencies for packaging tools (rpmbuild, dpkg-deb, pkgbuild, WiX) + # DotNet: Install the .NET SDK + # Both: Package and DotNet scenarios + # Tools: Install .NET global tools (e.g., dotnet-format) + # All: Install all dependencies (packaging, .NET SDK, and tools) + [ValidateSet("Package", "DotNet", "Both", "Tools", "All")] [string]$Scenario = "Package" ) @@ -2263,12 +2805,14 @@ function Start-PSBootstrap { try { if ($environment.IsLinux -or $environment.IsMacOS) { + Write-LogGroupStart -Title "Install Native Dependencies" # This allows sudo install to be optional; needed when running in containers / as root # Note that when it is null, Invoke-Expression (but not &) must be used to interpolate properly $sudo = if (!$NoSudo) { "sudo" } - if ($BuildLinuxArm -and $environment.IsLinux -and -not $environment.IsUbuntu) { - Write-Error "Cross compiling for linux-arm is only supported on Ubuntu environment" + if ($BuildLinuxArm -and $environment.IsLinux -and -not $environment.IsUbuntu -and -not $environment.IsMariner) { + Write-Error "Cross compiling for linux-arm is only supported on AzureLinux/Ubuntu environment" + Write-LogGroupEnd -Title "Install Native Dependencies" return } @@ -2283,7 +2827,9 @@ function Start-PSBootstrap { elseif ($environment.IsUbuntu18) { $Deps += "libicu60"} # Packaging tools - if ($Scenario -eq 'Both' -or $Scenario -eq 'Package') { $Deps += "ruby-dev", "groff", "libffi-dev", "rpm", "g++", "make" } + # Note: ruby-dev, libffi-dev, g++, and make are no longer needed for DEB packaging + # DEB packages now use native dpkg-deb (pre-installed) + if ($Scenario -eq 'Both' -or $Scenario -eq 'Package') { $Deps += "groff", "rpm" } # Install dependencies # change the fontend from apt-get to noninteractive @@ -2307,7 +2853,9 @@ function Start-PSBootstrap { $Deps += "libicu", "openssl-libs" # Packaging tools - if ($Scenario -eq 'Both' -or $Scenario -eq 'Package') { $Deps += "ruby-devel", "rpm-build", "groff", 'libffi-devel', "gcc-c++" } + # Note: ruby-devel and libffi-devel are no longer needed + # RPM packages use rpmbuild, DEB packages use dpkg-deb + if ($Scenario -eq 'Both' -or $Scenario -eq 'Package') { $Deps += "rpm-build", "groff" } $PackageManager = Get-RedHatPackageManager @@ -2328,7 +2876,8 @@ function Start-PSBootstrap { $Deps += "wget" # Packaging tools - if ($Scenario -eq 'Both' -or $Scenario -eq 'Package') { $Deps += "ruby-devel", "rpmbuild", "groff", 'libffi-devel', "gcc" } + # Note: ruby-devel and libffi-devel are no longer needed for packaging + if ($Scenario -eq 'Both' -or $Scenario -eq 'Package') { $Deps += "rpmbuild", "groff" } $PackageManager = "zypper --non-interactive install" $baseCommand = "$sudo $PackageManager" @@ -2367,16 +2916,42 @@ function Start-PSBootstrap { } } - # Install [fpm](https://github.com/jordansissel/fpm) - if ($Scenario -eq 'Both' -or $Scenario -eq 'Package') { - Install-GlobalGem -Sudo $sudo -GemName "dotenv" -GemVersion "2.8.1" - Install-GlobalGem -Sudo $sudo -GemName "ffi" -GemVersion "1.16.3" - Install-GlobalGem -Sudo $sudo -GemName "fpm" -GemVersion "1.15.1" - Install-GlobalGem -Sudo $sudo -GemName "rexml" -GemVersion "3.2.5" + if ($Scenario -in 'All', 'Both', 'Package') { + # For RPM-based systems, ensure rpmbuild is available + if ($environment.IsLinux -and ($environment.IsRedHatFamily -or $environment.IsSUSEFamily -or $environment.IsMariner)) { + Write-Verbose -Verbose "Checking for rpmbuild..." + if (!(Get-Command rpmbuild -ErrorAction SilentlyContinue)) { + Write-Warning "rpmbuild not found. Installing rpm-build package..." + Start-NativeExecution -sb ([ScriptBlock]::Create("$sudo $PackageManager install -y rpm-build")) -IgnoreExitcode + } + } + + # For Debian-based systems and Mariner, ensure dpkg-deb is available + if ($environment.IsLinux -and ($environment.IsDebianFamily -or $environment.IsMariner)) { + Write-Verbose -Verbose "Checking for dpkg-deb..." + if (!(Get-Command dpkg-deb -ErrorAction SilentlyContinue)) { + Write-Warning "dpkg-deb not found. Installing dpkg package..." + if ($environment.IsMariner) { + # For Mariner (Azure Linux), install the extended repo first to access dpkg. + Write-Verbose -verbose "BEGIN: /etc/os-release content:" + Get-Content /etc/os-release | Write-Verbose -verbose + Write-Verbose -verbose "END: /etc/os-release content" + + Write-Verbose -Verbose "Installing azurelinux-repos-extended for Mariner..." + + Start-NativeExecution -sb ([ScriptBlock]::Create("$sudo $PackageManager azurelinux-repos-extended")) -IgnoreExitcode -Verbose + Start-NativeExecution -sb ([ScriptBlock]::Create("$sudo $PackageManager dpkg")) -IgnoreExitcode -Verbose + } else { + Start-NativeExecution -sb ([ScriptBlock]::Create("$sudo apt-get install -y dpkg")) -IgnoreExitcode + } + } + } } + Write-LogGroupEnd -Title "Install Native Dependencies" } - if ($Scenario -eq 'DotNet' -or $Scenario -eq 'Both') { + if ($Scenario -in 'All', 'Both', 'DotNet') { + Write-LogGroupStart -Title "Install .NET SDK" Write-Verbose -Verbose "Calling Find-Dotnet from Start-PSBootstrap" @@ -2415,10 +2990,12 @@ function Start-PSBootstrap { else { Write-Log -message "dotnet is already installed. Skipping installation." } + Write-LogGroupEnd -Title "Install .NET SDK" } # Install Windows dependencies if `-Package` or `-BuildWindowsNative` is specified if ($environment.IsWindows) { + Write-LogGroupStart -Title "Install Windows Dependencies" ## The VSCode build task requires 'pwsh.exe' to be found in Path if (-not (Get-Command -Name pwsh.exe -CommandType Application -ErrorAction Ignore)) { @@ -2426,17 +3003,32 @@ function Start-PSBootstrap { $psInstallFile = [System.IO.Path]::Combine($PSScriptRoot, "tools", "install-powershell.ps1") & $psInstallFile -AddToPath } - if ($Scenario -eq 'Both' -or $Scenario -eq 'Package') { - Import-Module "$PSScriptRoot\tools\wix\wix.psm1" - $isArm64 = "$env:RUNTIME" -eq 'arm64' - Install-Wix -arm64:$isArm64 + Write-LogGroupEnd -Title "Install Windows Dependencies" + } + + # Ensure dotnet is available + Find-Dotnet + + if (-not $env:TF_BUILD) { + if ($Scenario -in 'All', 'Tools') { + Write-LogGroupStart -Title "Install .NET Global Tools" + Write-Log -message "Installing .NET global tools" + + # Install dotnet-format + Write-Verbose -Verbose "Installing dotnet-format global tool" + Start-NativeExecution { + dotnet tool install --global dotnet-format + } + Write-LogGroupEnd -Title "Install .NET Global Tools" } } if ($env:TF_BUILD) { + Write-LogGroupStart -Title "Capture NuGet Sources" Write-Verbose -Verbose "--- Start - Capturing nuget sources" dotnet nuget list source --format detailed Write-Verbose -Verbose "--- End - Capturing nuget sources" + Write-LogGroupEnd -Title "Capture NuGet Sources" } } finally { Pop-Location @@ -2446,6 +3038,17 @@ function Start-PSBootstrap { ## If the required SDK version is found, return it. ## Otherwise, return the latest installed SDK version that can be found. function Find-RequiredSDK { + <# + .SYNOPSIS + Returns the installed .NET SDK version that best satisfies the required version. + .DESCRIPTION + Lists installed SDKs with 'dotnet --list-sdks'. Returns the required version + string if it is installed; otherwise returns the newest installed SDK version. + .PARAMETER requiredSdkVersion + The exact .NET SDK version string to search for. + .OUTPUTS + System.String. The matched or newest installed SDK version string. + #> param( [Parameter(Mandatory, Position = 0)] [string] $requiredSdkVersion @@ -2470,6 +3073,28 @@ function Find-RequiredSDK { } function Start-DevPowerShell { + <# + .SYNOPSIS + Launches a PowerShell session using the locally built pwsh. + .DESCRIPTION + Starts a new pwsh process from the build output directory, optionally setting + the DEVPATH environment variable, redirecting PSModulePath to the built Modules + directory, and loading or suppressing the user profile. + .PARAMETER ArgumentList + Additional arguments passed to the pwsh process. + .PARAMETER LoadProfile + When specified, the user profile is loaded (by default -noprofile is prepended). + .PARAMETER Configuration + Build configuration whose output directory to use (ConfigurationParamSet). + .PARAMETER BinDir + Explicit path to the directory containing the pwsh binary (BinDirParamSet). + .PARAMETER NoNewWindow + Runs pwsh in the current console window instead of a new one. + .PARAMETER Command + A command string passed to pwsh via -command. + .PARAMETER KeepPSModulePath + Preserves the existing PSModulePath instead of redirecting it to the build output. + #> [CmdletBinding(DefaultParameterSetName='ConfigurationParamSet')] param( [string[]]$ArgumentList = @(), @@ -2537,6 +3162,16 @@ function Start-DevPowerShell { function Start-TypeGen { + <# + .SYNOPSIS + Generates the CorePsTypeCatalog type-catalog file. + .DESCRIPTION + Invokes the TypeCatalogGen .NET tool to produce CorePsTypeCatalog.cs, which maps + .NET types to their containing assemblies. The output .inc file name varies by + runtime to allow simultaneous builds on Windows and WSL. + .PARAMETER IncFileName + Name of the .inc file listing dependent assemblies. Defaults to 'powershell.inc'. + #> [CmdletBinding()] param ( @@ -2547,38 +3182,17 @@ function Start-TypeGen # Add .NET CLI tools to PATH Find-Dotnet - # This custom target depends on 'ResolveAssemblyReferencesDesignTime', whose definition can be found in the sdk folder. - # To find the available properties of '_ReferencesFromRAR' when switching to a new dotnet sdk, follow the steps below: - # 1. create a dummy project using the new dotnet sdk. - # 2. build the dummy project with this command: - # dotnet msbuild .\dummy.csproj /t:ResolveAssemblyReferencesDesignTime /fileLogger /noconsolelogger /v:diag - # 3. search '_ReferencesFromRAR' in the produced 'msbuild.log' file. You will find the properties there. - $GetDependenciesTargetPath = "$PSScriptRoot/src/Microsoft.PowerShell.SDK/obj/Microsoft.PowerShell.SDK.csproj.TypeCatalog.targets" - $GetDependenciesTargetValue = @' - - - - <_RefAssemblyPath Include="%(_ReferencesFromRAR.OriginalItemSpec)%3B" Condition=" '%(_ReferencesFromRAR.NuGetPackageId)' != 'Microsoft.Management.Infrastructure' "/> - - - - -'@ - New-Item -ItemType Directory -Path (Split-Path -Path $GetDependenciesTargetPath -Parent) -Force > $null - Set-Content -Path $GetDependenciesTargetPath -Value $GetDependenciesTargetValue -Force -Encoding Ascii - Push-Location "$PSScriptRoot/src/Microsoft.PowerShell.SDK" try { $ps_inc_file = "$PSScriptRoot/src/TypeCatalogGen/$IncFileName" - dotnet msbuild .\Microsoft.PowerShell.SDK.csproj /t:_GetDependencies "/property:DesignTimeBuild=true;_DependencyFile=$ps_inc_file" /nologo + Start-NativeExecution { dotnet msbuild .\Microsoft.PowerShell.SDK.csproj /t:_GetDependencies "/property:DesignTimeBuild=true;_DependencyFile=$ps_inc_file" /nologo } } finally { Pop-Location } Push-Location "$PSScriptRoot/src/TypeCatalogGen" try { - dotnet run ../System.Management.Automation/CoreCLR/CorePsTypeCatalog.cs $IncFileName + Start-NativeExecution { dotnet run ../System.Management.Automation/CoreCLR/CorePsTypeCatalog.cs $IncFileName } } finally { Pop-Location } @@ -2586,6 +3200,13 @@ function Start-TypeGen function Start-ResGen { + <# + .SYNOPSIS + Regenerates C# resource bindings from resx files. + .DESCRIPTION + Runs the ResGen .NET tool in src/ResGen to produce strongly-typed resource classes + for all resx files in the PowerShell project. + #> [CmdletBinding()] param() @@ -2600,7 +3221,75 @@ function Start-ResGen } } +function Add-PSEnvironmentPath { + <# + .SYNOPSIS + Adds a path to the process PATH and persists to GitHub Actions workflow if running in GitHub Actions + .PARAMETER Path + Path to add to PATH + .PARAMETER Prepend + If specified, prepends the path instead of appending + #> + param ( + [Parameter(Mandatory)] + [string]$Path, + + [switch]$Prepend + ) + + # Set in current process + if ($Prepend) { + $env:PATH = $Path + [IO.Path]::PathSeparator + $env:PATH + } else { + $env:PATH += [IO.Path]::PathSeparator + $Path + } + + # Persist to GitHub Actions workflow if running in GitHub Actions + if ($env:GITHUB_ACTIONS -eq 'true') { + Write-Verbose -Verbose "Adding $Path to GITHUB_PATH" + Add-Content -Path $env:GITHUB_PATH -Value $Path + } +} + +function Set-PSEnvironmentVariable { + <# + .SYNOPSIS + Sets an environment variable in the process and persists to GitHub Actions workflow if running in GitHub Actions + .PARAMETER Name + The name of the environment variable + .PARAMETER Value + The value of the environment variable + #> + param ( + [Parameter(Mandatory)] + [string]$Name, + + [Parameter(Mandatory)] + [string]$Value + ) + + # Set in current process + Set-Item -Path "env:$Name" -Value $Value + + # Persist to GitHub Actions workflow if running in GitHub Actions + if ($env:GITHUB_ACTIONS -eq 'true') { + Write-Verbose -Verbose "Setting $Name in GITHUB_ENV" + Add-Content -Path $env:GITHUB_ENV -Value "$Name=$Value" + } +} + function Find-Dotnet { + <# + .SYNOPSIS + Ensures the required .NET SDK is available on PATH. + .DESCRIPTION + Checks whether the dotnet currently on PATH can locate the required SDK version. + If not, prepends the user-local dotnet installation directory to PATH. + Optionally sets DOTNET_ROOT and adds the global tools directory to PATH. + .PARAMETER SetDotnetRoot + When specified, sets the DOTNET_ROOT environment variable and adds the + .NET global tools path to PATH. + #> param ( [switch] $SetDotnetRoot ) @@ -2630,25 +3319,40 @@ function Find-Dotnet { if ($dotnetCLIInstalledVersion -ne $chosenDotNetVersion) { Write-Warning "The 'dotnet' in the current path can't find SDK version ${dotnetCLIRequiredVersion}, prepending $dotnetPath to PATH." # Globally installed dotnet doesn't have the required SDK version, prepend the user local dotnet location - $env:PATH = $dotnetPath + [IO.Path]::PathSeparator + $env:PATH + Add-PSEnvironmentPath -Path $dotnetPath -Prepend if ($SetDotnetRoot) { Write-Verbose -Verbose "Setting DOTNET_ROOT to $dotnetPath" - $env:DOTNET_ROOT = $dotnetPath + Set-PSEnvironmentVariable -Name 'DOTNET_ROOT' -Value $dotnetPath } } elseif ($SetDotnetRoot) { Write-Verbose -Verbose "Expected dotnet version found, setting DOTNET_ROOT to $dotnetPath" - $env:DOTNET_ROOT = $dotnetPath + Set-PSEnvironmentVariable -Name 'DOTNET_ROOT' -Value $dotnetPath } } else { Write-Warning "Could not find 'dotnet', appending $dotnetPath to PATH." - $env:PATH += [IO.Path]::PathSeparator + $dotnetPath + Add-PSEnvironmentPath -Path $dotnetPath + + if ($SetDotnetRoot) { + Write-Verbose -Verbose "Setting DOTNET_ROOT to $dotnetPath" + Set-PSEnvironmentVariable -Name 'DOTNET_ROOT' -Value $dotnetPath + } } if (-not (precheck 'dotnet' "Still could not find 'dotnet', restoring PATH.")) { + # Give up, restore original PATH. There is nothing to persist since we didn't make a change. $env:PATH = $originalPath } + elseif ($SetDotnetRoot) { + # If we found dotnet, also add the global tools path to PATH + # Add .NET global tools to PATH when setting up the environment + $dotnetToolsPath = Join-Path $dotnetPath "tools" + if (Test-Path $dotnetToolsPath) { + Write-Verbose -Verbose "Adding .NET tools path to PATH: $dotnetToolsPath" + Add-PSEnvironmentPath -Path $dotnetToolsPath + } + } } <# @@ -2682,6 +3386,14 @@ function Convert-TxtResourceToXml } function script:Use-MSBuild { + <# + .SYNOPSIS + Ensures that the msbuild command is available in the current scope. + .DESCRIPTION + If msbuild is not found in PATH, creates a script-scoped alias pointing to the + .NET Framework 4 MSBuild at its standard Windows location. Throws if neither + location provides a usable msbuild. + #> # TODO: we probably should require a particular version of msbuild, if we are taking this dependency # msbuild v14 and msbuild v4 behaviors are different for XAML generation $frameworkMsBuildLocation = "${env:SystemRoot}\Microsoft.Net\Framework\v4.0.30319\msbuild" @@ -2701,6 +3413,18 @@ function script:Use-MSBuild { function script:Write-Log { + <# + .SYNOPSIS + Writes a colored message to the host, with optional error annotation. + .DESCRIPTION + In GitHub Actions, error messages are emitted as workflow error annotations + using the '::error::' command. Normal messages are written in green; errors + in red. Console colors are reset after each call. + .PARAMETER message + The text to write. + .PARAMETER isError + When specified, writes the message as an error (red / GitHub Actions annotation). + #> param ( [Parameter(Position=0, Mandatory)] @@ -2728,6 +3452,18 @@ function script:Write-Log } function script:Write-LogGroup { + <# + .SYNOPSIS + Emits a titled group of log messages wrapped in log-group markers. + .DESCRIPTION + Calls Write-LogGroupStart, writes each message line via Write-Log, then calls + Write-LogGroupEnd. In GitHub Actions this creates a collapsible group; on other + hosts it adds BEGIN/END banners. + .PARAMETER Message + One or more message lines to write inside the group. + .PARAMETER Title + The title displayed for the log group. + #> param ( [Parameter(Position = 0, Mandatory)] @@ -2750,6 +3486,15 @@ function script:Write-LogGroup { $script:logGroupColor = [System.ConsoleColor]::Cyan function script:Write-LogGroupStart { + <# + .SYNOPSIS + Opens a collapsible log group section. + .DESCRIPTION + In GitHub Actions emits '::group::'. On other hosts writes a colored + begin banner using the script-level log group color. + .PARAMETER Title + The label for the group. + #> param ( [Parameter(Mandatory)] @@ -2765,6 +3510,15 @@ function script:Write-LogGroupStart { } function script:Write-LogGroupEnd { + <# + .SYNOPSIS + Closes a collapsible log group section. + .DESCRIPTION + In GitHub Actions emits '::endgroup::'. On other hosts writes a colored + end banner using the script-level log group color. + .PARAMETER Title + The group label (used only in non-GitHub-Actions output). + #> param ( [Parameter(Mandatory)] @@ -2780,6 +3534,20 @@ function script:Write-LogGroupEnd { } function script:precheck([string]$command, [string]$missedMessage) { + <# + .SYNOPSIS + Tests whether a command exists on PATH and optionally emits a warning if missing. + .DESCRIPTION + Uses Get-Command to locate the specified command. Returns $true if found, + $false otherwise. If the command is absent and a message is provided, + Write-Warning is called with that message. + .PARAMETER command + The command name to look for. + .PARAMETER missedMessage + Warning text to emit when the command is not found. Pass $null to suppress it. + .OUTPUTS + System.Boolean. $true when the command is found; $false otherwise. + #> $c = Get-Command $command -ErrorAction Ignore if (-not $c) { if (-not [string]::IsNullOrEmpty($missedMessage)) @@ -2795,6 +3563,13 @@ function script:precheck([string]$command, [string]$missedMessage) { # Cleans the PowerShell repo - everything but the root folder function Clear-PSRepo { + <# + .SYNOPSIS + Cleans all subdirectories of the PowerShell repository using 'git clean -fdX'. + .DESCRIPTION + Iterates over every top-level directory under the repository root and removes all + files that are not tracked by git, including ignored files. + #> [CmdletBinding()] param() @@ -2807,6 +3582,20 @@ function Clear-PSRepo # Install PowerShell modules such as PackageManagement, PowerShellGet function Copy-PSGalleryModules { + <# + .SYNOPSIS + Copies PowerShell Gallery modules from the NuGet cache to a Modules directory. + .DESCRIPTION + Reads the PackageReference items in the specified csproj file, resolves each + package from the NuGet global cache, and copies it to the destination directory. + Package nupkg and metadata files are excluded from the copy. + .PARAMETER CsProjPath + Path to the csproj file whose PackageReference items describe Gallery modules. + .PARAMETER Destination + Destination Modules directory. Must end with 'Modules'. + .PARAMETER Force + Forces NuGet package restore even if packages are already present. + #> [CmdletBinding()] param( [Parameter(Mandatory=$true)] @@ -2866,6 +3655,22 @@ function Copy-PSGalleryModules function Merge-TestLogs { + <# + .SYNOPSIS + Merges xUnit and NUnit test log files into a single xUnit XML file. + .DESCRIPTION + Converts NUnit Pester logs to xUnit assembly format and appends them, along with + any additional xUnit logs, to the primary xUnit log. The merged result is saved + to the specified output path. + .PARAMETER XUnitLogPath + Path to the primary xUnit XML log file. + .PARAMETER NUnitLogPath + One or more NUnit (Pester) XML log file paths to merge in. + .PARAMETER AdditionalXUnitLogPath + Optional additional xUnit XML log files to append. + .PARAMETER OutputLogPath + Path for the merged xUnit output file. + #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] @@ -2907,6 +3712,23 @@ function Merge-TestLogs } function ConvertFrom-PesterLog { + <# + .SYNOPSIS + Converts Pester NUnit XML log files to xUnit assembly format. + .DESCRIPTION + Accepts one or more NUnit log files produced by Pester, or existing xUnit logs, + and converts them to an in-memory xUnit assembly object model. If multiple logs + are provided and -MultipleLog is not set, they are combined into a single + assemblies object. + .PARAMETER Logfile + Path(s) to the NUnit or xUnit log file(s) to convert. Accepts pipeline input. + .PARAMETER IncludeEmpty + When specified, includes test assemblies that contain zero test cases. + .PARAMETER MultipleLog + When specified, returns one assemblies object per log file instead of combining. + .OUTPUTS + assemblies. One or more xUnit assemblies objects containing converted test data. + #> [CmdletBinding()] param ( [Parameter(ValueFromPipeline = $true, Mandatory = $true, Position = 0)] @@ -2914,21 +3736,6 @@ function ConvertFrom-PesterLog { [Parameter()][switch]$IncludeEmpty, [Parameter()][switch]$MultipleLog ) - <# -Convert our test logs to -xunit schema - top level assemblies -Pester conversion -foreach $r in "test-results"."test-suite".results."test-suite" -assembly - name = $r.Description - config-file = log file (this is the only way we can determine between admin/nonadmin log) - test-framework = Pester - environment = top-level "test-results.environment.platform - run-date = date (doesn't exist in pester except for beginning) - run-time = time - time = -#> - BEGIN { # CLASSES class assemblies { @@ -3275,6 +4082,17 @@ assembly # Save PSOptions to be restored by Restore-PSOptions function Save-PSOptions { + <# + .SYNOPSIS + Persists the current PSOptions to a JSON file. + .DESCRIPTION + Serializes the current build options (or the supplied Options object) to JSON + and writes them to the specified path. Defaults to psoptions.json in the repo root. + .PARAMETER PSOptionsPath + Path to the JSON file to write. Defaults to '$PSScriptRoot/psoptions.json'. + .PARAMETER Options + PSOptions object to save. Defaults to the current build options. + #> param( [ValidateScript({$parent = Split-Path $_;if($parent){Test-Path $parent}else{return $true}})] [ValidateNotNullOrEmpty()] @@ -3292,6 +4110,17 @@ function Save-PSOptions { # Restore PSOptions # Optionally remove the PSOptions file function Restore-PSOptions { + <# + .SYNOPSIS + Loads saved PSOptions from a JSON file and makes them the active build options. + .DESCRIPTION + Reads the JSON file produced by Save-PSOptions, reconstructs a PSOptions + hashtable, and stores it via Set-PSOptions. Optionally deletes the file afterward. + .PARAMETER PSOptionsPath + Path to the JSON file to read. Defaults to '$PSScriptRoot/psoptions.json'. + .PARAMETER Remove + When specified, deletes the JSON file after loading. + #> param( [ValidateScript({Test-Path $_})] [string] @@ -3324,6 +4153,31 @@ function Restore-PSOptions { function New-PSOptionsObject { + <# + .SYNOPSIS + Constructs the PSOptions hashtable from individual build-option components. + .DESCRIPTION + Assembles the hashtable consumed by Start-PSBuild, Restore-PSPackage, and related + commands. Prefer New-PSOptions, which auto-computes fields such as the output path. + .PARAMETER RootInfo + PSCustomObject with repo root path validation metadata. + .PARAMETER Top + Path to the top-level project directory (pwsh source directory). + .PARAMETER Runtime + The .NET runtime identifier (RID) for the build. + .PARAMETER Configuration + The build configuration: Debug, Release, CodeCoverage, or StaticAnalysis. + .PARAMETER PSModuleRestore + Whether Gallery modules should be restored to the build output. + .PARAMETER Framework + The target .NET framework moniker, e.g. 'net11.0'. + .PARAMETER Output + Full path to the output pwsh executable. + .PARAMETER ForMinimalSize + Whether this is a minimal-size build. + .OUTPUTS + System.Collections.Hashtable. A PSOptions hashtable. + #> param( [PSCustomObject] $RootInfo, @@ -3494,6 +4348,17 @@ $script:RESX_TEMPLATE = @' '@ function Get-UniquePackageFolderName { + <# + .SYNOPSIS + Returns a unique temporary folder path for a test package under the specified root. + .DESCRIPTION + Tries the path '<Root>/TestPackage' first, then appends a random numeric suffix + until an unused path is found. Throws if a unique name cannot be found in 10 tries. + .PARAMETER Root + The parent directory under which the unique folder name is generated. + .OUTPUTS + System.String. A path under Root that does not yet exist. + #> param( [Parameter(Mandatory)] $Root ) @@ -3520,6 +4385,18 @@ function Get-UniquePackageFolderName { function New-TestPackage { + <# + .SYNOPSIS + Creates a zip archive containing all test content and test tools. + .DESCRIPTION + Builds and publishes test tools, copies the test directory, assets directory, + and resx resource directories into a temporary staging folder, then zips the + staging folder to TestPackage.zip in the specified destination directory. + .PARAMETER Destination + Directory where the TestPackage.zip file is created. + .PARAMETER Runtime + The .NET runtime identifier (RID) used when publishing test tool executables. + #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] @@ -3596,6 +4473,16 @@ class NugetPackageSource { } function New-NugetPackageSource { + <# + .SYNOPSIS + Creates a NugetPackageSource object with the given URL and name. + .PARAMETER Url + The NuGet feed URL. + .PARAMETER Name + The feed name used as the key in nuget.config. + .OUTPUTS + NugetPackageSource. An object with Url and Name properties. + #> param( [Parameter(Mandatory = $true)] [string]$Url, [Parameter(Mandatory = $true)] [string] $Name @@ -3606,6 +4493,22 @@ function New-NugetPackageSource { $script:NuGetEndpointCredentials = [System.Collections.Generic.Dictionary[String,System.Object]]::new() function New-NugetConfigFile { + <# + .SYNOPSIS + Generates a nuget.config file at the specified destination. + .DESCRIPTION + Creates a nuget.config XML file with the supplied package sources and optional + credentials. The generated file is marked as skip-worktree in git to prevent + accidental commits of feed credentials. + .PARAMETER NugetPackageSource + One or more NugetPackageSource objects defining the feeds to include. + .PARAMETER Destination + Directory where nuget.config is written. + .PARAMETER UserName + Username for authenticated feed access. + .PARAMETER ClearTextPAT + Personal access token in clear text for authenticated feed access. + #> param( [Parameter(Mandatory = $true, ParameterSetName ='user')] [Parameter(Mandatory = $true, ParameterSetName ='nouser')] @@ -3687,10 +4590,24 @@ function New-NugetConfigFile { } function Clear-PipelineNugetAuthentication { + <# + .SYNOPSIS + Clears cached NuGet feed credentials used by the pipeline. + .DESCRIPTION + Removes all entries from the script-scoped NuGetEndpointCredentials dictionary. + #> $script:NuGetEndpointCredentials.Clear() } function Set-PipelineNugetAuthentication { + <# + .SYNOPSIS + Publishes cached NuGet feed credentials to the Azure DevOps pipeline. + .DESCRIPTION + Serializes the script-scoped NuGetEndpointCredentials dictionary to JSON and sets + the VSS_NUGET_EXTERNAL_FEED_ENDPOINTS pipeline variable so that subsequent NuGet + operations authenticate automatically. + #> $endpointcredentials = @() foreach ($key in $script:NuGetEndpointCredentials.Keys) { @@ -3705,6 +4622,14 @@ function Set-PipelineNugetAuthentication { function Set-CorrectLocale { + <# + .SYNOPSIS + Configures the Linux locale to en_US.UTF-8 for consistent build behavior. + .DESCRIPTION + On Ubuntu 20+ systems, generates the en_US.UTF-8 locale and sets LC_ALL and LANG + environment variables. Skips execution on non-Linux platforms and Ubuntu versions + earlier than 20. + #> Write-LogGroupStart -Title "Set-CorrectLocale" if (-not $IsLinux) @@ -3741,6 +4666,13 @@ function Set-CorrectLocale } function Write-Locale { + <# + .SYNOPSIS + Writes the current system locale settings to the log output. + .DESCRIPTION + Runs the 'locale' command on Linux or macOS and writes the output inside a + collapsible log group. Does nothing on Windows. + #> if (-not $IsLinux -and -not $IsMacOS) { Write-Verbose -Message "only supported on Linux and macOS" -Verbose return @@ -3752,6 +4684,13 @@ function Write-Locale { } function Install-AzCopy { + <# + .SYNOPSIS + Downloads and installs AzCopy v10 on Windows. + .DESCRIPTION + Downloads the AzCopy v10 zip archive from the official Microsoft URL and extracts + it to the Agent tools directory. Skips installation if AzCopy is already present. + #> $testPath = "C:\Program Files (x86)\Microsoft SDKs\Azure\AzCopy\AzCopy.exe" if (Test-Path $testPath) { Write-Verbose "AzCopy already installed" -Verbose @@ -3766,6 +4705,15 @@ function Install-AzCopy { } function Find-AzCopy { + <# + .SYNOPSIS + Locates the AzCopy executable on the system. + .DESCRIPTION + Searches several well-known installation paths for AzCopy.exe and falls back to + Get-Command if none of the paths contain the executable. + .OUTPUTS + System.String. The full path to the AzCopy executable. + #> $searchPaths = @('$(Agent.ToolsDirectory)\azcopy10\AzCopy.exe', "C:\Program Files (x86)\Microsoft SDKs\Azure\AzCopy\AzCopy.exe", "C:\azcopy10\AzCopy.exe") foreach ($filter in $searchPaths) { @@ -3781,6 +4729,16 @@ function Find-AzCopy { function Clear-NativeDependencies { + <# + .SYNOPSIS + Removes unnecessary native dependency files from the publish output. + .DESCRIPTION + Strips architecture-specific DiaSym reader DLLs that are not needed for the + target runtime from both the publish folder and the pwsh.deps.json manifest. + Skips fxdependent runtimes where no cleanup is needed. + .PARAMETER PublishFolder + Path to the publish output directory containing pwsh.deps.json. + #> param( [Parameter(Mandatory=$true)] [string] $PublishFolder ) @@ -3815,7 +4773,7 @@ function Clear-NativeDependencies $filesToDeleteWinDesktop = @() $deps = Get-Content "$PublishFolder/pwsh.deps.json" -Raw | ConvertFrom-Json -Depth 20 - $targetRuntime = ".NETCoreApp,Version=v10.0/$($script:Options.Runtime)" + $targetRuntime = ".NETCoreApp,Version=v11.0/$($script:Options.Runtime)" $runtimePackNetCore = $deps.targets.${targetRuntime}.PSObject.Properties.Name -like 'runtimepack.Microsoft.NETCore.App.Runtime*' $runtimePackWinDesktop = $deps.targets.${targetRuntime}.PSObject.Properties.Name -like 'runtimepack.Microsoft.WindowsDesktop.App.Runtime*' @@ -3847,6 +4805,14 @@ function Clear-NativeDependencies function Update-DotNetSdkVersion { +<# + .SYNOPSIS + Updates the .NET SDK version in global.json and DotnetRuntimeMetadata.json. + .DESCRIPTION + Queries the official .NET SDK feed for the latest version in the current channel + and writes the new version to global.json and DotnetRuntimeMetadata.json. + #> + param() $globalJsonPath = "$PSScriptRoot/global.json" $globalJson = get-content $globalJsonPath | convertfrom-json $oldVersion = $globalJson.sdk.version @@ -3866,6 +4832,17 @@ function Update-DotNetSdkVersion { } function Set-PipelineVariable { + <# + .SYNOPSIS + Sets an Azure DevOps pipeline variable and the corresponding environment variable. + .DESCRIPTION + Emits a ##vso[task.setvariable] logging command so that subsequent pipeline steps + can access the variable, and also sets it in the current process environment. + .PARAMETER Name + The pipeline variable name. + .PARAMETER Value + The value to assign. + #> param( [parameter(Mandatory)] [string] $Name, diff --git a/docs/building/linux.md b/docs/building/linux.md index d1ec01bd206..55d96e4c21a 100644 --- a/docs/building/linux.md +++ b/docs/building/linux.md @@ -5,7 +5,7 @@ We'll start by showing how to set up your environment from scratch. ## Environment -These instructions are written assuming the Ubuntu 16.04 LTS, since that's the distro the team uses. +These instructions are written assuming Ubuntu 24.04 LTS (the CI uses ubuntu-latest). The build module works on a best-effort basis for other distributions. ### Git Setup @@ -41,15 +41,13 @@ In PowerShell: ```powershell Import-Module ./build.psm1 -Start-PSBootstrap +Start-PSBootstrap -Scenario Both ``` The `Start-PSBootstrap` function does the following: -- Adds the LLVM package feed -- Installs our dependencies combined with the dependencies of the .NET CLI toolchain via `apt-get` -- Uninstalls any prior versions of .NET CLI -- Downloads and installs the .NET Core SDK 2.0.0 to `~/.dotnet` +- Installs build dependencies and packaging tools via `apt-get` (or equivalent package manager) +- Downloads and installs the .NET SDK (currently version 10.0.100-rc.1) to `~/.dotnet` If you want to use `dotnet` outside of `Start-PSBuild`, add `~/.dotnet` to your `PATH` environment variable. @@ -66,10 +64,12 @@ Import-Module ./build.psm1 Start-PSBuild -UseNuGetOrg ``` +> The PowerShell project by default references packages from the private Azure Artifacts feed, which requires authentication. The `-UseNuGetOrg` flag reconfigures the build to use the public NuGet.org feed instead. + Congratulations! If everything went right, PowerShell is now built. The `Start-PSBuild` script will output the location of the executable: -`./src/powershell-unix/bin/Debug/net6.0/linux-x64/publish/pwsh`. +`./src/powershell-unix/bin/Debug/net11.0/linux-x64/publish/pwsh`. You should now be running the PowerShell Core that you just built, if you run the above executable. You can run our cross-platform Pester tests with `Start-PSPester -UseNuGetOrg`, and our xUnit tests with `Start-PSxUnit`. diff --git a/docs/building/macos.md b/docs/building/macos.md index 4f15e3fb547..7fd767ffa1c 100644 --- a/docs/building/macos.md +++ b/docs/building/macos.md @@ -37,3 +37,5 @@ We cannot do this for you in the build module due to #[847][]. Start a PowerShell session by running `pwsh`, and then use `Start-PSBuild -UseNuGetOrg` from the module. After building, PowerShell will be at `./src/powershell-unix/bin/Debug/net6.0/osx-x64/publish/pwsh`. + +> The PowerShell project by default references packages from the private Azure Artifacts feed, which requires authentication. The `-UseNuGetOrg` flag reconfigures the build to use the public NuGet.org feed instead. diff --git a/docs/building/windows-core.md b/docs/building/windows-core.md index 48e5c951633..20774695fcd 100644 --- a/docs/building/windows-core.md +++ b/docs/building/windows-core.md @@ -61,6 +61,8 @@ Import-Module ./build.psm1 Start-PSBuild -Clean -PSModuleRestore -UseNuGetOrg ``` +> The PowerShell project by default references packages from the private Azure Artifacts feed, which requires authentication. The `-UseNuGetOrg` flag reconfigures the build to use the public NuGet.org feed instead. + Congratulations! If everything went right, PowerShell is now built and executable as `./src/powershell-win-core/bin/Debug/net6.0/win7-x64/publish/pwsh.exe`. This location is of the form `./[project]/bin/[configuration]/[framework]/[rid]/publish/[binary name]`, diff --git a/docs/maintainers/releasing.md b/docs/maintainers/releasing.md index 5aae87582c9..ccb4e5529d7 100644 --- a/docs/maintainers/releasing.md +++ b/docs/maintainers/releasing.md @@ -57,26 +57,29 @@ It **requires** that PowerShell Core has been built via `Start-PSBuild` from the #### Windows -The `Start-PSPackage` function delegates to `New-MSIPackage` which creates a Windows Installer Package of PowerShell. +`Start-PSPackage` supports creating ZIP and MSIX packages for Windows. +When called without `-Type` on Windows, it defaults to creating both ZIP and MSIX packages. The packages *must* be published in release mode, so make sure `-Configuration Release` is specified when running `Start-PSBuild`. -It uses the Windows Installer XML Toolset (WiX) to generate a MSI package, -which copies the output of the published PowerShell files to a version-specific folder in Program Files, -and installs a shortcut in the Start Menu. -It can be uninstalled through `Programs and Features`. - Note that PowerShell is always self-contained, thus using it does not require installing it. -The output of `Start-PSBuild` includes a `powershell.exe` executable which can simply be launched. +The output of `Start-PSBuild` includes a `pwsh.exe` executable which can simply be launched. #### Linux / macOS The `Start-PSPackage` function delegates to `New-UnixPackage`. -It relies on the [Effing Package Management][fpm] project, -which makes building packages for any (non-Windows) platform a breeze. -Similarly, the PowerShell man-page is generated from the Markdown-like file + +For **Linux** (Debian-based distributions), it relies on the [Effing Package Management][fpm] project, +which makes building packages a breeze. + +For **macOS**, it uses native packaging tools (`pkgbuild` and `productbuild`) from Xcode Command Line Tools, +eliminating the need for Ruby or fpm. + +For **Linux** (Red Hat-based distributions), it uses `rpmbuild` directly. + +The PowerShell man-page is generated from the Markdown-like file [`assets/pwsh.1.ronn`][man] using [Ronn][]. -The function `Start-PSBootstrap -Package` will install both these tools. +The function `Start-PSBootstrap -Package` will install these tools. To modify any property of the packages, edit the `New-UnixPackage` function. Please also refer to the function for details on the package properties @@ -131,7 +134,7 @@ Without `-Name` specified, the primary `powershell` package will instead be created. [fpm]: https://github.com/jordansissel/fpm -[man]: ../../assets/pwsh.1.ronn +[man]: ../../assets/manpage/pwsh.1.ronn [ronn]: https://github.com/rtomayko/ronn ### Build and Packaging Examples @@ -162,8 +165,8 @@ Start-PSBuild -Clean -CrossGen -PSModuleRestore -Runtime win7-x64 -Configuration ```powershell # Create packages for v6.0.0-beta.1 release targeting Windows universal package. # 'win7-x64' / 'win7-x86' should be used for -WindowsRuntime. -Start-PSPackage -Type msi -ReleaseTag v6.0.0-beta.1 -WindowsRuntime 'win7-x64' Start-PSPackage -Type zip -ReleaseTag v6.0.0-beta.1 -WindowsRuntime 'win7-x64' +Start-PSPackage -Type msix -ReleaseTag v6.0.0-beta.1 -WindowsRuntime 'win7-x64' ``` ## NuGet Packages diff --git a/dsc/pwsh.profile.dsc.resource.json b/dsc/pwsh.profile.dsc.resource.json new file mode 100644 index 00000000000..aa5f5c29eee --- /dev/null +++ b/dsc/pwsh.profile.dsc.resource.json @@ -0,0 +1,126 @@ +{ + "$schema": "https://aka.ms/dsc/schemas/v3/bundled/resource/manifest.json", + "description": "Manage PowerShell profiles.", + "tags": [ + "Linux", + "Windows", + "macOS", + "PowerShell" + ], + "type": "Microsoft.PowerShell/Profile", + "version": "0.1.0", + "get": { + "executable": "pwsh", + "args": [ + "-NoLogo", + "-NonInteractive", + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-File", + "./pwsh.profile.resource.ps1", + "-operation", + "get" + ], + "input": "stdin" + }, + "set": { + "executable": "pwsh", + "args": [ + "-NoLogo", + "-NonInteractive", + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-File", + "./pwsh.profile.resource.ps1", + "-operation", + "set" + ], + "input": "stdin" + }, + "export": { + "executable": "pwsh", + "args": [ + "-NoLogo", + "-NonInteractive", + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-File", + "./pwsh.profile.resource.ps1", + "-operation", + "export" + ], + "input": "stdin" + }, + "exitCodes": { + "0": "Success", + "1": "Error", + "2": "Input not supported for export operation" + }, + "schema": { + "embedded": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Profile", + "description": "Manage PowerShell profiles.", + "type": "object", + "unevaluatedProperties": false, + "required": [ + "profileType" + ], + "properties": { + "profileType": { + "type": "string", + "title": "Profile Type", + "description": "Defines which profile to manage. Valid values are: 'AllUsersCurrentHost', 'AllUsersAllHosts', 'CurrentUserAllHosts', and 'CurrentUserCurrentHost'.", + "enum": [ + "AllUsersCurrentHost", + "AllUsersAllHosts", + "CurrentUserAllHosts", + "CurrentUserCurrentHost" + ] + }, + "profilePath": { + "title": "Profile Path", + "description": "The full path to the profile file.", + "type": "string", + "readOnly": true + }, + "content": { + "title": "Content", + "description": "Defines the content of the profile. If you don't specify this property, the resource doesn't manage the file contents. If you specify this property as an empty string, the resource removes all content from the file. If you specify this property as a non-empty string, the resource sets the file contents to the specified string. The resources retains newlines from this property without any modification.", + "type": [ "string", "null" ] + }, + "_exist": { + "$ref": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/resource/properties/exist.json" + }, + "_name": { + "$ref": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/resource/properties/name.json" + } + }, + "$defs": { + "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/resource/properties/exist.json": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/resource/properties/exist.json", + "title": "Instance should exist", + "description": "Indicates whether the DSC resource instance should exist.", + "type": "boolean", + "default": true, + "enum": [ + false, + true + ] + }, + "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/resource/properties/name.json": { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/resource/properties/name.json", + "title": "Exported instance name", + "description": "Returns a generated name for the resource instance from an export operation.", + "readOnly": true, + "type": "string" + } + } + } + } +} diff --git a/dsc/pwsh.profile.resource.ps1 b/dsc/pwsh.profile.resource.ps1 new file mode 100644 index 00000000000..ad9cfa4a63a --- /dev/null +++ b/dsc/pwsh.profile.resource.ps1 @@ -0,0 +1,179 @@ +## Copyright (c) Microsoft Corporation. All rights reserved. +## Licensed under the MIT License. + +[CmdletBinding()] +param( + [Parameter(Mandatory = $true)] + [ValidateSet('get', 'set', 'export')] + [string]$Operation, + [Parameter(ValueFromPipeline)] + [string[]]$UserInput +) + +Begin { + enum ProfileType { + AllUsersCurrentHost + AllUsersAllHosts + CurrentUserAllHosts + CurrentUserCurrentHost + } + + function New-PwshResource { + param( + [Parameter(Mandatory = $true)] + [ProfileType] $ProfileType, + + [Parameter(ParameterSetName = 'WithContent')] + [string] $Content, + + [Parameter(ParameterSetName = 'WithContent')] + [bool] $Exist + ) + + # Create the PSCustomObject with properties + $resource = [PSCustomObject]@{ + profileType = $ProfileType + content = $null + profilePath = GetProfilePath -profileType $ProfileType + _exist = $false + } + + # Add ToJson method + $resource | Add-Member -MemberType ScriptMethod -Name 'ToJson' -Value { + return ([ordered] @{ + profileType = $this.profileType + content = $this.content + profilePath = $this.profilePath + _exist = $this._exist + }) | ConvertTo-Json -Compress -EnumsAsStrings + } + + # Constructor logic - if Content and Exist parameters are provided (WithContent parameter set) + if ($PSCmdlet.ParameterSetName -eq 'WithContent') { + $resource.content = $Content + $resource._exist = $Exist + } else { + # Default constructor logic - read from file system + $fileExists = Test-Path $resource.profilePath + if ($fileExists) { + $resource.content = Get-Content -Path $resource.profilePath + } else { + $resource.content = $null + } + $resource._exist = $fileExists + } + + return $resource + } + + function GetProfilePath { + param ( + [ProfileType] $profileType + ) + + $path = switch ($profileType) { + 'AllUsersCurrentHost' { $PROFILE.AllUsersCurrentHost } + 'AllUsersAllHosts' { $PROFILE.AllUsersAllHosts } + 'CurrentUserAllHosts' { $PROFILE.CurrentUserAllHosts } + 'CurrentUserCurrentHost' { $PROFILE.CurrentUserCurrentHost } + } + + return $path + } + + function ExportOperation { + $allUserCurrentHost = New-PwshResource -ProfileType 'AllUsersCurrentHost' + $allUsersAllHost = New-PwshResource -ProfileType 'AllUsersAllHosts' + $currentUserAllHost = New-PwshResource -ProfileType 'CurrentUserAllHosts' + $currentUserCurrentHost = New-PwshResource -ProfileType 'CurrentUserCurrentHost' + + # Cannot use the ToJson() method here as we are adding a note property + $allUserCurrentHost | Add-Member -NotePropertyName '_name' -NotePropertyValue 'AllUsersCurrentHost' -PassThru | ConvertTo-Json -Compress -EnumsAsStrings + $allUsersAllHost | Add-Member -NotePropertyName '_name' -NotePropertyValue 'AllUsersAllHosts' -PassThru | ConvertTo-Json -Compress -EnumsAsStrings + $currentUserAllHost | Add-Member -NotePropertyName '_name' -NotePropertyValue 'CurrentUserAllHosts' -PassThru | ConvertTo-Json -Compress -EnumsAsStrings + $currentUserCurrentHost | Add-Member -NotePropertyName '_name' -NotePropertyValue 'CurrentUserCurrentHost' -PassThru | ConvertTo-Json -Compress -EnumsAsStrings + } + + function GetOperation { + param ( + [Parameter(Mandatory = $true)] + $InputResource, + [Parameter()] + [switch] $AsJson + ) + + $profilePath = GetProfilePath -profileType $InputResource.profileType.ToString() + + $actualState = New-PwshResource -ProfileType $InputResource.profileType + + $actualState.profilePath = $profilePath + + $exists = Test-Path $profilePath + + if ($InputResource._exist -and $exists) { + $content = Get-Content -Path $profilePath + $actualState.Content = $content + } elseif ($InputResource._exist -and -not $exists) { + $actualState.Content = $null + $actualState._exist = $false + } elseif (-not $InputResource._exist -and $exists) { + $actualState.Content = Get-Content -Path $profilePath + $actualState._exist = $true + } else { + $actualState.Content = $null + $actualState._exist = $false + } + + if ($AsJson) { + return $actualState.ToJson() + } else { + return $actualState + } + } + + function SetOperation { + param ( + $InputResource + ) + + $actualState = GetOperation -InputResource $InputResource + + if ($InputResource._exist) { + if (-not $actualState._exist) { + $null = New-Item -Path $actualState.profilePath -ItemType File -Force + } + + if ($null -ne $InputResource.content) { + Set-Content -Path $actualState.profilePath -Value $InputResource.content + } + } elseif ($actualState._exist) { + Remove-Item -Path $actualState.profilePath -Force + } + } +} +End { + $inputJson = $input | ConvertFrom-Json + + if ($inputJson) { + $InputResource = New-PwshResource -ProfileType $inputJson.profileType -Content $inputJson.content -Exist $inputJson._exist + } + + switch ($Operation) { + 'get' { + GetOperation -InputResource $InputResource -AsJson + } + 'set' { + SetOperation -InputResource $InputResource + } + 'export' { + if ($inputJson) { + Write-Error "Input not supported for export operation" + exit 2 + } + + ExportOperation + } + } + + exit 0 +} diff --git a/es-metadata.yml b/es-metadata.yml new file mode 100644 index 00000000000..24da115c114 --- /dev/null +++ b/es-metadata.yml @@ -0,0 +1,12 @@ +schemaVersion: 1.0.0 +providers: +- provider: InventoryAsCode + version: 1.0.0 + metadata: + isProduction: true + accountableOwners: + service: cef1de07-99d6-45df-b907-77d0066032ec + routing: + defaultAreaPath: + org: msazure + path: One\MGMT\Compute\Powershell\Powershell\Powershell Core\pwsh diff --git a/experimental-feature-linux.json b/experimental-feature-linux.json index ca5b49878a4..31f7b965a5b 100644 --- a/experimental-feature-linux.json +++ b/experimental-feature-linux.json @@ -2,6 +2,7 @@ "PSFeedbackProvider", "PSLoadAssemblyFromNativeCode", "PSNativeWindowsTildeExpansion", + "PSProfileDSCResource", "PSSerializeJSONLongEnumAsNumber", "PSRedirectToVariable", "PSSubsystemPluginModel" diff --git a/experimental-feature-windows.json b/experimental-feature-windows.json index ca5b49878a4..31f7b965a5b 100644 --- a/experimental-feature-windows.json +++ b/experimental-feature-windows.json @@ -2,6 +2,7 @@ "PSFeedbackProvider", "PSLoadAssemblyFromNativeCode", "PSNativeWindowsTildeExpansion", + "PSProfileDSCResource", "PSSerializeJSONLongEnumAsNumber", "PSRedirectToVariable", "PSSubsystemPluginModel" diff --git a/global.json b/global.json index ec0599543c4..d31f5220d83 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "10.0.100-preview.6.25358.103" + "version": "11.0.100-preview.3.26207.106" } } diff --git a/src/GlobalTools/PowerShell.Windows.x64/PowerShell.Windows.x64.csproj b/src/GlobalTools/PowerShell.Windows.x64/PowerShell.Windows.x64.csproj index 49d607ebfed..8449c58ebb0 100644 --- a/src/GlobalTools/PowerShell.Windows.x64/PowerShell.Windows.x64.csproj +++ b/src/GlobalTools/PowerShell.Windows.x64/PowerShell.Windows.x64.csproj @@ -2,7 +2,7 @@ <PropertyGroup> <OutputType>Exe</OutputType> - <TargetFramework>net10.0</TargetFramework> + <TargetFramework>net11.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <PackAsTool>true</PackAsTool> diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimAsyncOperation.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimAsyncOperation.cs index 023bf652b1d..e794fd929d1 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimAsyncOperation.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimAsyncOperation.cs @@ -381,7 +381,7 @@ protected object GetReferenceOrReferenceArrayObject(object value, ref CimType re if (value is PSReference cimReference) { object baseObject = GetBaseObject(cimReference.Value); - if (!(baseObject is CimInstance cimInstance)) + if (baseObject is not CimInstance cimInstance) { return null; } @@ -403,7 +403,7 @@ protected object GetReferenceOrReferenceArrayObject(object value, ref CimType re CimInstance[] cimInstanceArray = new CimInstance[cimReferenceArray.Length]; for (int i = 0; i < cimReferenceArray.Length; i++) { - if (!(cimReferenceArray[i] is PSReference tempCimReference)) + if (cimReferenceArray[i] is not PSReference tempCimReference) { return null; } diff --git a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSessionProxy.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSessionProxy.cs index 630bb34dfb1..5cdc168ae24 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSessionProxy.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimSessionProxy.cs @@ -2041,7 +2041,7 @@ protected override bool PreNewActionEvent(CmdletActionEventArgs args) } CimWriteResultObject writeResultObject = args.Action as CimWriteResultObject; - if (!(writeResultObject.Result is CimClass cimClass)) + if (writeResultObject.Result is not CimClass cimClass) { return true; } @@ -2200,7 +2200,7 @@ protected override bool PreNewActionEvent(CmdletActionEventArgs args) } CimWriteResultObject writeResultObject = args.Action as CimWriteResultObject; - if (!(writeResultObject.Result is CimInstance cimInstance)) + if (writeResultObject.Result is not CimInstance cimInstance) { return true; } diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/ColumnPicker.xaml.cs b/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/ColumnPicker.xaml.cs index 8a919959968..05151330ea2 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/ColumnPicker.xaml.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/ColumnPicker.xaml.cs @@ -60,7 +60,7 @@ internal ColumnPicker( : this() { ArgumentNullException.ThrowIfNull(columns); - + ArgumentNullException.ThrowIfNull(availableColumns); // Add visible columns to Selected list, preserving order diff --git a/src/Microsoft.Management.UI.Internal/commandHelpers/ShowCommandHelper.cs b/src/Microsoft.Management.UI.Internal/commandHelpers/ShowCommandHelper.cs index 32c68e961c6..10690b65dc7 100644 --- a/src/Microsoft.Management.UI.Internal/commandHelpers/ShowCommandHelper.cs +++ b/src/Microsoft.Management.UI.Internal/commandHelpers/ShowCommandHelper.cs @@ -679,7 +679,8 @@ internal static string SingleQuote(string str) /// <returns>The host window, if it is present or null if it is not.</returns> internal static Window GetHostWindow(PSCmdlet cmdlet) { - PSPropertyInfo windowProperty = cmdlet.Host.PrivateData.Properties["Window"]; + // The value of 'PrivateData' property may be null for the default host or a custom host. + PSPropertyInfo windowProperty = cmdlet.Host.PrivateData?.Properties["Window"]; if (windowProperty == null) { return null; diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/CounterFileInfo.cs b/src/Microsoft.PowerShell.Commands.Diagnostics/CounterFileInfo.cs index cbebb9b4557..2b11b6d3b03 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/CounterFileInfo.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/CounterFileInfo.cs @@ -54,4 +54,3 @@ public UInt32 SampleCount private UInt32 _sampleCount = 0; } } - diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/GetEventCommand.cs b/src/Microsoft.PowerShell.Commands.Diagnostics/GetEventCommand.cs index 3aec30c9e4c..0ce68a31d73 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/GetEventCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/GetEventCommand.cs @@ -225,10 +225,6 @@ public sealed class GetWinEventCommand : PSCmdlet ValueFromPipelineByPropertyName = false, ParameterSetName = "XmlQuerySet", HelpMessageBaseName = "GetEventResources")] - [SuppressMessage("Microsoft.Design", "CA1059:MembersShouldNotExposeCertainConcreteTypes", - Scope = "member", - Target = "Microsoft.PowerShell.Commands.GetEvent.FilterXml", - Justification = "An XmlDocument is required here because that is the type Powershell supports")] public XmlDocument FilterXml { get; set; } /// <summary> @@ -522,9 +518,11 @@ private void ProcessListLog() || (wildLogPattern.IsMatch(logName))) { + EventLogConfiguration logObj; + EventLogInformation logInfoObj; try { - EventLogConfiguration logObj = new(logName, eventLogSession); + logObj = new EventLogConfiguration(logName, eventLogSession); // // Skip direct channels matching the wildcard unless -Force is present. @@ -537,19 +535,25 @@ private void ProcessListLog() continue; } - EventLogInformation logInfoObj = eventLogSession.GetLogInformation(logName, PathType.LogName); - - PSObject outputObj = new(logObj); + bMatchFound = true; + logInfoObj = eventLogSession.GetLogInformation(logName, PathType.LogName); + } + catch (UnauthorizedAccessException exc) + { + string exceptionMsg = string.Format(CultureInfo.InvariantCulture, GetEventResources.LogInfoNoAccess, logName); + var newExc = new UnauthorizedAccessException(exceptionMsg, exc); - outputObj.Properties.Add(new PSNoteProperty("FileSize", logInfoObj.FileSize)); - outputObj.Properties.Add(new PSNoteProperty("IsLogFull", logInfoObj.IsLogFull)); - outputObj.Properties.Add(new PSNoteProperty("LastAccessTime", logInfoObj.LastAccessTime)); - outputObj.Properties.Add(new PSNoteProperty("LastWriteTime", logInfoObj.LastWriteTime)); - outputObj.Properties.Add(new PSNoteProperty("OldestRecordNumber", logInfoObj.OldestRecordNumber)); - outputObj.Properties.Add(new PSNoteProperty("RecordCount", logInfoObj.RecordCount)); + string recommendationMsg = GetEventResources.SuggestElevation; + var eRecord = new ErrorRecord(newExc, "LogInfoNoAccess", ErrorCategory.PermissionDenied, logName) + { + ErrorDetails = new ErrorDetails(string.Empty) + { + RecommendedAction = recommendationMsg + } + }; - WriteObject(outputObj); - bMatchFound = true; + WriteError(eRecord); + continue; } catch (Exception exc) { @@ -560,6 +564,16 @@ private void ProcessListLog() WriteError(new ErrorRecord(outerExc, "LogInfoUnavailable", ErrorCategory.NotSpecified, null)); continue; } + + PSObject outputObj = new(logObj); + outputObj.Properties.Add(new PSNoteProperty("FileSize", logInfoObj.FileSize)); + outputObj.Properties.Add(new PSNoteProperty("IsLogFull", logInfoObj.IsLogFull)); + outputObj.Properties.Add(new PSNoteProperty("LastAccessTime", logInfoObj.LastAccessTime)); + outputObj.Properties.Add(new PSNoteProperty("LastWriteTime", logInfoObj.LastWriteTime)); + outputObj.Properties.Add(new PSNoteProperty("OldestRecordNumber", logInfoObj.OldestRecordNumber)); + outputObj.Properties.Add(new PSNoteProperty("RecordCount", logInfoObj.RecordCount)); + + WriteObject(outputObj); } } diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/ImportCounterCommand.cs b/src/Microsoft.PowerShell.Commands.Diagnostics/ImportCounterCommand.cs index ed4cdb51ff9..729334d0605 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/ImportCounterCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/ImportCounterCommand.cs @@ -679,4 +679,3 @@ private void WriteSampleSetObject(PerformanceCounterSampleSet set, bool firstSet } } } - diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj b/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj index 95c3c8b5390..9552f72c83a 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/Microsoft.PowerShell.Commands.Diagnostics.csproj @@ -8,7 +8,7 @@ <ItemGroup> <ProjectReference Include="..\System.Management.Automation\System.Management.Automation.csproj" /> - <PackageReference Include="System.Diagnostics.PerformanceCounter" Version="10.0.0-preview.6.25358.103" /> + <PackageReference Include="System.Diagnostics.PerformanceCounter" Version="11.0.0-preview.3.26207.106" /> </ItemGroup> <PropertyGroup> diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/NewWinEventCommand.cs b/src/Microsoft.PowerShell.Commands.Diagnostics/NewWinEventCommand.cs index b0b102870c4..cdddba939f3 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/NewWinEventCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/NewWinEventCommand.cs @@ -323,7 +323,7 @@ protected override void EndProcessing() } } - internal class EventWriteException : Exception + internal sealed class EventWriteException : Exception { internal EventWriteException(string msg, Exception innerException) : base(msg, innerException) diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/PdhHelper.cs b/src/Microsoft.PowerShell.Commands.Diagnostics/PdhHelper.cs index b761aa6e30b..6f5d8f6e5ec 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/PdhHelper.cs +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/PdhHelper.cs @@ -190,7 +190,7 @@ internal struct CounterHandleNInstance public string InstanceName; } - internal class PdhHelper : IDisposable + internal sealed class PdhHelper : IDisposable { [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct PDH_COUNTER_PATH_ELEMENTS diff --git a/src/Microsoft.PowerShell.Commands.Diagnostics/resources/GetEventResources.resx b/src/Microsoft.PowerShell.Commands.Diagnostics/resources/GetEventResources.resx index f8f41ecc2f5..5c66f2b9e94 100644 --- a/src/Microsoft.PowerShell.Commands.Diagnostics/resources/GetEventResources.resx +++ b/src/Microsoft.PowerShell.Commands.Diagnostics/resources/GetEventResources.resx @@ -255,6 +255,12 @@ The defined template is following: <data name="LogInfoUnavailable" xml:space="preserve"> <value>To access the '{0}' log start PowerShell with elevated user rights. Error: {1}</value> </data> + <data name="LogInfoNoAccess" xml:space="preserve"> + <value>Access denied for log: '{0}'.</value> + </data> + <data name="SuggestElevation" xml:space="preserve"> + <value>Launch PowerShell with elevated user rights.</value> + </data> <data name="NoEventMessage" xml:space="preserve"> <value>Cannot retrieve event message text.</value> </data> @@ -285,4 +291,25 @@ The defined template is following: <data name="LogCountLimitExceeded" xml:space="preserve"> <value>Log count ({0}) is exceeded Windows Event Log API limit ({1}). Adjust filter to return less log names.</value> </data> + <data name="ListLogParamHelp" xml:space="preserve"> + <value>Specifies the event logs. Wildcards are permitted.</value> + </data> + <data name="GetLogParamHelp" xml:space="preserve"> + <value>Specifies the event logs that this cmdlet gets events from. Wildcards are permitted.</value> + </data> + <data name="ListProviderParamHelp" xml:space="preserve"> + <value>Specifies the event log providers that this cmdlet gets.</value> + </data> + <data name="GetProviderParamHelp" xml:space="preserve"> + <value>Specifies the event log providers from which this cmdlet gets events.</value> + </data> + <data name="PathParamHelp" xml:space="preserve"> + <value>Specifies the path to the event log files that this cmdlet gets events from.</value> + </data> + <data name="MaxEventsParamHelp" xml:space="preserve"> + <value>Specifies the maximum number of events that are returned.</value> + </data> + <data name="ComputerNameParamHelp" xml:space="preserve"> + <value>Specifies the name of the computer from which this cmdlet gets data.</value> + </data> </root> diff --git a/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj b/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj index 5813d5d4d3e..8ce2a97d67b 100644 --- a/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj +++ b/src/Microsoft.PowerShell.Commands.Management/Microsoft.PowerShell.Commands.Management.csproj @@ -47,7 +47,7 @@ <ItemGroup> <!-- the following package(s) are from https://github.com/dotnet/corefx --> - <PackageReference Include="System.ServiceProcess.ServiceController" Version="10.0.0-preview.6.25358.103" /> + <PackageReference Include="System.ServiceProcess.ServiceController" Version="11.0.0-preview.3.26207.106" /> </ItemGroup> </Project> diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/CimJobException.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/CimJobException.cs index 675c6c71404..935cc960c6c 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/CimJobException.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/CimJobException.cs @@ -49,7 +49,7 @@ public CimJobException(string message, Exception inner) : base(message, inner) /// </summary> /// <param name="info">The <see cref="SerializationInfo"/> that holds the serialized object data about the exception being thrown.</param> /// <param name="context">The <see cref="StreamingContext"/> that contains contextual information about the source or destination.</param> - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected CimJobException( SerializationInfo info, StreamingContext context) diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/CreateInstanceJob.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/CreateInstanceJob.cs index d8a1bdd44ea..eb594dd4eea 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/CreateInstanceJob.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/CreateInstanceJob.cs @@ -12,7 +12,7 @@ namespace Microsoft.PowerShell.Cmdletization.Cim /// <summary> /// Job wrapping invocation of a CreateInstance intrinsic CIM method. /// </summary> - internal class CreateInstanceJob : PropertySettingJob<CimInstance> + internal sealed class CreateInstanceJob : PropertySettingJob<CimInstance> { private CimInstance _resultFromCreateInstance; private CimInstance _resultFromGetInstance; diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/DeleteInstanceJob.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/DeleteInstanceJob.cs index 327fb83affa..0432c6c9a8f 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/DeleteInstanceJob.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/DeleteInstanceJob.cs @@ -12,7 +12,7 @@ namespace Microsoft.PowerShell.Cmdletization.Cim /// <summary> /// Job wrapping invocation of a DeleteInstance intrinsic CIM method. /// </summary> - internal class DeleteInstanceJob : MethodInvocationJobBase<object> + internal sealed class DeleteInstanceJob : MethodInvocationJobBase<object> { private readonly CimInstance _objectToDelete; diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/EnumerateAssociatedInstancesJob.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/EnumerateAssociatedInstancesJob.cs index bd024b7d76a..569740d0d1b 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/EnumerateAssociatedInstancesJob.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/EnumerateAssociatedInstancesJob.cs @@ -14,7 +14,7 @@ namespace Microsoft.PowerShell.Cmdletization.Cim /// <summary> /// Job that handles executing a WQL (in the future CQL?) query on a remote CIM server. /// </summary> - internal class EnumerateAssociatedInstancesJob : QueryJobBase + internal sealed class EnumerateAssociatedInstancesJob : QueryJobBase { private readonly CimInstance _associatedObject; private readonly string _associationName; diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/InstanceMethodInvocationJob.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/InstanceMethodInvocationJob.cs index cd1eb78d81c..06a45933522 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/InstanceMethodInvocationJob.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/InstanceMethodInvocationJob.cs @@ -13,7 +13,7 @@ namespace Microsoft.PowerShell.Cmdletization.Cim /// <summary> /// Job wrapping invocation of an extrinsic CIM method. /// </summary> - internal class InstanceMethodInvocationJob : ExtrinsicMethodInvocationJob + internal sealed class InstanceMethodInvocationJob : ExtrinsicMethodInvocationJob { private readonly CimInstance _targetInstance; diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/ModifyInstanceJob.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/ModifyInstanceJob.cs index f0d07fc201d..869002a55f4 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/ModifyInstanceJob.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/ModifyInstanceJob.cs @@ -13,7 +13,7 @@ namespace Microsoft.PowerShell.Cmdletization.Cim /// <summary> /// Job wrapping invocation of a ModifyInstance intrinsic CIM method. /// </summary> - internal class ModifyInstanceJob : PropertySettingJob<CimInstance> + internal sealed class ModifyInstanceJob : PropertySettingJob<CimInstance> { private CimInstance _resultFromModifyInstance; private bool _resultFromModifyInstanceHasBeenPassedThru; diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/QueryJob.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/QueryJob.cs index 7c7a82ee9ca..7bd2bd17531 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/QueryJob.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/QueryJob.cs @@ -14,7 +14,7 @@ namespace Microsoft.PowerShell.Cmdletization.Cim /// <summary> /// Job that handles executing a WQL (in the future CQL?) query on a remote CIM server. /// </summary> - internal class QueryInstancesJob : QueryJobBase + internal sealed class QueryInstancesJob : QueryJobBase { private readonly string _wqlQuery; private readonly bool _useEnumerateInstances; diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/StaticMethodInvocationJob.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/StaticMethodInvocationJob.cs index 5a25206fe94..3bc376ab3d5 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/StaticMethodInvocationJob.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/StaticMethodInvocationJob.cs @@ -11,7 +11,7 @@ namespace Microsoft.PowerShell.Cmdletization.Cim /// <summary> /// Job wrapping invocation of a static CIM method. /// </summary> - internal class StaticMethodInvocationJob : ExtrinsicMethodInvocationJob + internal sealed class StaticMethodInvocationJob : ExtrinsicMethodInvocationJob { internal StaticMethodInvocationJob(CimJobContext jobContext, MethodInvocationInfo methodInvocationInfo) : base(jobContext, false /* passThru */, jobContext.CmdletizationClassName, methodInvocationInfo) diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimChildJobBase.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimChildJobBase.cs index d787389e9f0..6bd2bee7fee 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimChildJobBase.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimChildJobBase.cs @@ -84,7 +84,7 @@ private enum WsManErrorCode : uint private static bool IsWsManQuotaReached(Exception exception) { - if (!(exception is CimException cimException)) + if (exception is not CimException cimException) { return false; } @@ -1002,7 +1002,7 @@ private CimResponseType PromptUserCallback(string message, CimPromptType promptT internal static bool IsShowComputerNameMarkerPresent(CimInstance cimInstance) { PSObject pso = PSObject.AsPSObject(cimInstance); - if (!(pso.InstanceMembers[RemotingConstants.ShowComputerNameNoteProperty] is PSPropertyInfo psShowComputerNameProperty)) + if (pso.InstanceMembers[RemotingConstants.ShowComputerNameNoteProperty] is not PSPropertyInfo psShowComputerNameProperty) { return false; } diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimCmdletDefinitionContext.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimCmdletDefinitionContext.cs index c334ff29601..8a4e4272561 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimCmdletDefinitionContext.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimCmdletDefinitionContext.cs @@ -10,7 +10,7 @@ namespace Microsoft.PowerShell.Cmdletization.Cim { - internal class CimCmdletDefinitionContext + internal sealed class CimCmdletDefinitionContext { internal CimCmdletDefinitionContext( string cmdletizationClassName, diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimCmdletInvocationContext.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimCmdletInvocationContext.cs index c876184c141..3bee74526fc 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimCmdletInvocationContext.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimCmdletInvocationContext.cs @@ -10,7 +10,7 @@ namespace Microsoft.PowerShell.Cmdletization.Cim { - internal class CimCmdletInvocationContext + internal sealed class CimCmdletInvocationContext { internal CimCmdletInvocationContext( CimCmdletDefinitionContext cmdletDefinitionContext, diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimConverter.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimConverter.cs index 17a085b7ce5..da075286c34 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimConverter.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimConverter.cs @@ -24,7 +24,7 @@ namespace Microsoft.PowerShell.Cim { - internal class CimSensitiveValueConverter : IDisposable + internal sealed class CimSensitiveValueConverter : IDisposable { private sealed class SensitiveString : IDisposable { diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimJobContext.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimJobContext.cs index 5f01d472554..27d48ccab9a 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimJobContext.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimJobContext.cs @@ -9,7 +9,7 @@ namespace Microsoft.PowerShell.Cmdletization.Cim { - internal class CimJobContext + internal sealed class CimJobContext { internal CimJobContext( CimCmdletInvocationContext cmdletInvocationContext, diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimQuery.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimQuery.cs index a065f79204f..f08c2ab3c11 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimQuery.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimQuery.cs @@ -18,7 +18,7 @@ namespace Microsoft.PowerShell.Cmdletization.Cim /// <summary> /// CimQuery supports building of queries against CIM object model. /// </summary> - internal class CimQuery : QueryBuilder, ISessionBoundQueryBuilder<CimSession> + internal sealed class CimQuery : QueryBuilder, ISessionBoundQueryBuilder<CimSession> { private readonly StringBuilder _wqlCondition; @@ -315,7 +315,7 @@ public override void FilterByAssociatedInstance(object associatedInstance, strin public override void AddQueryOption(string optionName, object optionValue) { ArgumentException.ThrowIfNullOrEmpty(optionName); - ArgumentNullException.ThrowIfNull(optionValue); + ArgumentNullException.ThrowIfNull(optionValue); this.queryOptions[optionName] = optionValue; } diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimWrapper.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimWrapper.cs index c812778456a..472046b192f 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimWrapper.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/cimWrapper.cs @@ -168,7 +168,7 @@ private CimJobContext CreateJobContext(CimSession session, object targetObject) /// <returns><see cref="System.Management.Automation.Job"/> object that performs a query against the wrapped object model.</returns> internal override StartableJob CreateQueryJob(CimSession session, QueryBuilder baseQuery) { - if (!(baseQuery is CimQuery query)) + if (baseQuery is not CimQuery query) { throw new ArgumentNullException(nameof(baseQuery)); } diff --git a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/clientSideQuery.cs b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/clientSideQuery.cs index e8e4d46e075..c1a8552db8c 100644 --- a/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/clientSideQuery.cs +++ b/src/Microsoft.PowerShell.Commands.Management/cimSupport/cmdletization/cim/clientSideQuery.cs @@ -20,9 +20,9 @@ namespace Microsoft.PowerShell.Cmdletization.Cim /// 1) filtering that cannot be translated into a server-side query (i.e. when CimQuery.WildcardToWqlLikeOperand reports that it cannot translate into WQL) /// 2) detecting if all expected results have been received and giving friendly user errors otherwise (i.e. could not find process with name='foo'; details in Windows 8 Bugs: #60926) /// </summary> - internal class ClientSideQuery : QueryBuilder + internal sealed class ClientSideQuery : QueryBuilder { - internal class NotFoundError + internal sealed class NotFoundError { public NotFoundError() { @@ -530,7 +530,7 @@ private static bool WildcardEqual(string propertyName, object actualPropertyValu } } - internal class PropertyValueExcludeFilter : PropertyValueRegularFilter + internal sealed class PropertyValueExcludeFilter : PropertyValueRegularFilter { public PropertyValueExcludeFilter(string propertyName, object expectedPropertyValue, bool wildcardsEnabled, BehaviorOnNoMatch behaviorOnNoMatch) : base(propertyName, expectedPropertyValue, wildcardsEnabled, behaviorOnNoMatch) @@ -548,7 +548,7 @@ protected override bool IsMatchingValue(object actualPropertyValue) } } - internal class PropertyValueMinFilter : PropertyValueFilter + internal sealed class PropertyValueMinFilter : PropertyValueFilter { public PropertyValueMinFilter(string propertyName, object expectedPropertyValue, BehaviorOnNoMatch behaviorOnNoMatch) : base(propertyName, expectedPropertyValue, behaviorOnNoMatch) @@ -569,7 +569,7 @@ private static bool ActualValueGreaterThanOrEqualToExpectedValue(string property { try { - if (!(expectedPropertyValue is IComparable expectedComparable)) + if (expectedPropertyValue is not IComparable expectedComparable) { return false; } @@ -583,7 +583,7 @@ private static bool ActualValueGreaterThanOrEqualToExpectedValue(string property } } - internal class PropertyValueMaxFilter : PropertyValueFilter + internal sealed class PropertyValueMaxFilter : PropertyValueFilter { public PropertyValueMaxFilter(string propertyName, object expectedPropertyValue, BehaviorOnNoMatch behaviorOnNoMatch) : base(propertyName, expectedPropertyValue, behaviorOnNoMatch) @@ -604,7 +604,7 @@ private static bool ActualValueLessThanOrEqualToExpectedValue(string propertyNam { try { - if (!(actualPropertyValue is IComparable actualComparable)) + if (actualPropertyValue is not IComparable actualComparable) { return false; } diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/CIMHelper.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/CIMHelper.cs index 035d5b17bbe..688b6362e16 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/CIMHelper.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/CIMHelper.cs @@ -132,7 +132,7 @@ internal static string WqlQueryAll(string from) internal static T[] GetAll<T>(CimSession session, string nameSpace, string wmiClassName) where T : class, new() { ArgumentException.ThrowIfNullOrEmpty(wmiClassName); - + var rv = new List<T>(); try diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/CombinePathCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/CombinePathCommand.cs index 71ef5df8f08..79b1c862bfd 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/CombinePathCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/CombinePathCommand.cs @@ -51,6 +51,21 @@ public class JoinPathCommand : CoreCommandWithCredentialsBase [Parameter] public SwitchParameter Resolve { get; set; } + /// <summary> + /// Gets or sets the extension to use for the resulting path. + /// If not specified, the original extension (if any) is preserved. + /// <para> + /// Behavior: + /// - If the path has an existing extension, it will be replaced with the specified extension. + /// - If the path does not have an extension, the specified extension will be added. + /// - If an empty string is provided, any existing extension will be removed. + /// - A leading dot in the extension is optional; if omitted, one will be added automatically. + /// </para> + /// </summary> + [Parameter(ValueFromPipelineByPropertyName = true)] + [ValidateNotNull] + public string Extension { get; set; } + #endregion Parameters #region Command code @@ -128,6 +143,12 @@ protected override void ProcessRecord() continue; } + // If Extension parameter is present it is not null due to [ValidateNotNull]. + if (Extension is not null) + { + joinedPath = System.IO.Path.ChangeExtension(joinedPath, Extension.Length == 0 ? null : Extension); + } + if (Resolve) { // Resolve the paths. The default API (GetResolvedPSPathFromPSPath) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/CommitTransactionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/CommitTransactionCommand.cs index 6f93119c999..94e923bfa6e 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/CommitTransactionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/CommitTransactionCommand.cs @@ -28,4 +28,3 @@ protected override void EndProcessing() } } } - diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Computer.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Computer.cs index 3b791d37a1a..ea8c111531c 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Computer.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Computer.cs @@ -24,10 +24,6 @@ using Microsoft.Win32; using Dbg = System.Management.Automation; -// FxCop suppressions for resource strings: -[module: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", Scope = "resource", Target = "ComputerResources.resources", MessageId = "unjoined")] -[module: SuppressMessage("Microsoft.Naming", "CA1701:ResourceStringCompoundWordsShouldBeCasedCorrectly", Scope = "resource", Target = "ComputerResources.resources", MessageId = "UpTime")] - namespace Microsoft.PowerShell.Commands { #region Restart-Computer @@ -2023,35 +2019,24 @@ internal static void WriteNonTerminatingError(int errorcode, PSCmdlet cmdlet, st /// <returns></returns> internal static bool IsComputerNameValid(string computerName) { - bool allDigits = true; + bool hasAsciiLetterOrHyphen = false; if (computerName.Length >= 64) return false; foreach (char t in computerName) { - if (t >= 'A' && t <= 'Z' || - t >= 'a' && t <= 'z') + if (char.IsAsciiLetter(t) || t is '-') { - allDigits = false; - continue; - } - else if (t >= '0' && t <= '9') - { - continue; + hasAsciiLetterOrHyphen = true; } - else if (t == '-') - { - allDigits = false; - continue; - } - else + else if (!char.IsAsciiDigit(t)) { return false; } } - return !allDigits; + return hasAsciiLetterOrHyphen; } /// <summary> diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/ComputerUnix.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/ComputerUnix.cs index eb286f7c190..22092116330 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/ComputerUnix.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ComputerUnix.cs @@ -158,7 +158,7 @@ protected override void StopProcessing() /// <summary> /// Run shutdown command. /// </summary> - protected void RunShutdown(String args) + protected void RunShutdown(string args) { if (shutdownPath is null) { diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/ContentCommandBase.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/ContentCommandBase.cs index 87c7731ed70..e7b3ada4ebb 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/ContentCommandBase.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ContentCommandBase.cs @@ -244,7 +244,7 @@ internal void WriteContentObject(object content, long readCount, PathInfo pathIn /// as they get written to the pipeline. An instance of this cache class is /// only valid for a single path. /// </summary> - internal class ContentPathsCache + internal sealed class ContentPathsCache { /// <summary> /// Constructs a content cache item. diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/ControlPanelItemCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/ControlPanelItemCommand.cs index fffb36d2979..3734eb82b0c 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/ControlPanelItemCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ControlPanelItemCommand.cs @@ -300,7 +300,7 @@ internal void GetCategoryMap() foreach (ShellFolderItem category in catItems) { string path = category.Path; - string catNum = path.Substring(path.LastIndexOf("\\", StringComparison.OrdinalIgnoreCase) + 1); + string catNum = path.Substring(path.LastIndexOf('\\') + 1); CategoryMap.Add(catNum, category.Name); } diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Eventlog.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Eventlog.cs index 3731687599c..c667116fdd0 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Eventlog.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Eventlog.cs @@ -555,7 +555,7 @@ private bool FiltersMatch(EventLogEntry entry) } } - if (!usernamematch) + if (!usernamematch) { return usernamematch; } @@ -595,7 +595,7 @@ private bool FiltersMatch(EventLogEntry entry) } } - if (!datematch) + if (!datematch) { return datematch; } @@ -1465,4 +1465,3 @@ protected override void BeginProcessing() #endregion RemoveEventLogCommand } - diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetClipboardCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetClipboardCommand.cs index dd878bd72ce..a2b7f03ce70 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetClipboardCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetClipboardCommand.cs @@ -2,8 +2,10 @@ // Licensed under the MIT License. using System; +using System.Collections; using System.Collections.Generic; using System.Management.Automation; +using System.Management.Automation.Language; using Microsoft.PowerShell.Commands.Internal; namespace Microsoft.PowerShell.Commands @@ -34,6 +36,13 @@ public SwitchParameter Raw } } + /// <summary> + /// Gets or sets the delimiters to use when splitting the clipboard content. + /// </summary> + [Parameter] + [ArgumentCompleter(typeof(DelimiterCompleter))] + public string[] Delimiter { get; set; } = [Environment.NewLine]; + private bool _raw; /// <summary> @@ -68,11 +77,40 @@ private List<string> GetClipboardContentAsText() } else { - string[] splitSymbol = { Environment.NewLine }; - result.AddRange(textContent.Split(splitSymbol, StringSplitOptions.None)); + result.AddRange(textContent.Split(Delimiter, StringSplitOptions.None)); } return result; } } + + /// <summary> + /// Provides argument completion for the Delimiter parameter. + /// </summary> + public sealed class DelimiterCompleter : IArgumentCompleter + { + /// <summary> + /// Provides argument completion for the Delimiter parameter. + /// </summary> + /// <param name="commandName">The name of the command that is being completed.</param> + /// <param name="parameterName">The name of the parameter that is being completed.</param> + /// <param name="wordToComplete">The input text to filter the results by.</param> + /// <param name="commandAst">The ast of the command that triggered the completion.</param> + /// <param name="fakeBoundParameters">The parameters bound to the command.</param> + /// <returns>Completion results.</returns> + public IEnumerable<CompletionResult> CompleteArgument(string commandName, string parameterName, string wordToComplete, CommandAst commandAst, IDictionary fakeBoundParameters) + { + wordToComplete ??= string.Empty; + var pattern = new WildcardPattern(wordToComplete + '*', WildcardOptions.IgnoreCase); + if (pattern.IsMatch("CRLF") || pattern.IsMatch("Windows")) + { + yield return new CompletionResult("\"`r`n\"", "CRLF", CompletionResultType.ParameterValue, "Windows (CRLF)"); + } + + if (pattern.IsMatch("LF") || pattern.IsMatch("Unix") || pattern.IsMatch("Linux")) + { + yield return new CompletionResult("\"`n\"", "LF", CompletionResultType.ParameterValue, "UNIX (LF)"); + } + } + } } diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetComputerInfoCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetComputerInfoCommand.cs index 3de7c723745..87f96c436ea 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetComputerInfoCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetComputerInfoCommand.cs @@ -1335,7 +1335,7 @@ protected static string GetLanguageName(uint? lcid) #pragma warning disable 649 // fields and properties in these class are assigned dynamically [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiBaseBoard + internal sealed class WmiBaseBoard { public string Caption; public string[] ConfigOptions; @@ -1368,7 +1368,7 @@ internal class WmiBaseBoard } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiBios : WmiClassBase + internal sealed class WmiBios : WmiClassBase { public ushort[] BiosCharacteristics; public string[] BIOSVersion; @@ -1403,7 +1403,7 @@ internal class WmiBios : WmiClassBase } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiComputerSystem + internal sealed class WmiComputerSystem { public ushort? AdminPasswordStatus; public bool? AutomaticManagedPagefile; @@ -1486,7 +1486,7 @@ public PowerManagementCapabilities[] GetPowerManagementCapabilities() } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiDeviceGuard + internal sealed class WmiDeviceGuard { public uint[] AvailableSecurityProperties; public uint? CodeIntegrityPolicyEnforcementStatus; @@ -1561,7 +1561,7 @@ public DeviceGuard AsOutputType } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiKeyboard + internal sealed class WmiKeyboard { public ushort? Availability; public string Caption; @@ -1588,14 +1588,14 @@ internal class WmiKeyboard } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WMiLogicalMemory + internal sealed class WMiLogicalMemory { // TODO: fill this in!!! public uint? TotalPhysicalMemory; } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiMsftNetAdapter + internal sealed class WmiMsftNetAdapter { public string Caption; public string Description; @@ -1683,7 +1683,7 @@ internal class WmiMsftNetAdapter } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiNetworkAdapter + internal sealed class WmiNetworkAdapter { public string AdapterType; public ushort? AdapterTypeID; @@ -1727,7 +1727,7 @@ internal class WmiNetworkAdapter } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiNetworkAdapterConfiguration + internal sealed class WmiNetworkAdapterConfiguration { public bool? ArpAlwaysSourceRoute; public bool? ArpUseEtherSNAP; @@ -1793,7 +1793,7 @@ internal class WmiNetworkAdapterConfiguration } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiOperatingSystem : WmiClassBase + internal sealed class WmiOperatingSystem : WmiClassBase { #region Fields public string BootDevice; @@ -1900,7 +1900,7 @@ private static OSProductSuite[] MakeProductSuites(uint? suiteMask) } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiPageFileUsage + internal sealed class WmiPageFileUsage { public uint? AllocatedBaseSize; public string Caption; @@ -1914,7 +1914,7 @@ internal class WmiPageFileUsage } [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Class is instantiated directly from a CIM instance")] - internal class WmiProcessor + internal sealed class WmiProcessor { public ushort? AddressWidth; public ushort? Architecture; @@ -1977,7 +1977,7 @@ internal class WmiProcessor #endregion Intermediate WMI classes #region Other Intermediate classes - internal class RegWinNtCurrentVersion + internal sealed class RegWinNtCurrentVersion { public string BuildLabEx; public string CurrentVersion; @@ -3685,7 +3685,7 @@ public enum DeviceGuardHardwareSecure /// Secure Memory Overwrite. /// </summary> SecureMemoryOverwrite = 4, - + /// <summary> /// UEFI Code Readonly. /// </summary> diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetTransactionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetTransactionCommand.cs index 484ddb96680..6b6551619f1 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetTransactionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetTransactionCommand.cs @@ -23,4 +23,3 @@ protected override void EndProcessing() } } } - diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetWMIObjectCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetWMIObjectCommand.cs index 85d8c4feb3f..27f812c7a80 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/GetWMIObjectCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/GetWMIObjectCommand.cs @@ -438,4 +438,3 @@ private string GetClassNameFromQuery(string query) #endregion Command code } } - diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Hotfix.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Hotfix.cs index 1aa94509469..d0f15346396 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Hotfix.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Hotfix.cs @@ -206,26 +206,11 @@ private bool FilterMatch(ManagementObject obj) #region "IDisposable Members" /// <summary> - /// Dispose Method. + /// Release all resources. /// </summary> public void Dispose() { - this.Dispose(true); - // Use SuppressFinalize in case a subclass - // of this type implements a finalizer. - GC.SuppressFinalize(this); - } - - /// <summary> - /// Dispose Method. - /// </summary> - /// <param name="disposing"></param> - public void Dispose(bool disposing) - { - if (disposing) - { - _searchProcess?.Dispose(); - } + _searchProcess?.Dispose(); } #endregion "IDisposable Members" diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Navigation.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Navigation.cs index f4ba55202ed..6ab8f1d652d 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Navigation.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Navigation.cs @@ -2718,7 +2718,7 @@ protected override void ProcessRecord() { // Get the localized prompt string - string prompt = StringUtil.Format(NavigationResources.RemoveItemWithChildren, resolvedPath.Path); + string prompt = StringUtil.Format(NavigationResources.RemoveItemWithChildren, providerPath); // Confirm the user wants to remove all children and the item even if // they did not specify -recurse diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/ParsePathCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/ParsePathCommand.cs index b6609f8b692..ca616301ebb 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/ParsePathCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ParsePathCommand.cs @@ -289,137 +289,125 @@ protected override void ProcessRecord() { string result = null; - switch (ParameterSetName) + // Check switch parameters in order of specificity + if (IsAbsolute) { - case isAbsoluteSet: - string ignored; - bool isPathAbsolute = - SessionState.Path.IsPSAbsolute(pathsToParse[index], out ignored); + string ignored; + bool isPathAbsolute = + SessionState.Path.IsPSAbsolute(pathsToParse[index], out ignored); - WriteObject(isPathAbsolute); - continue; + WriteObject(isPathAbsolute); + continue; + } + else if (Qualifier) + { + int separatorIndex = pathsToParse[index].IndexOf(':'); - case qualifierSet: - int separatorIndex = pathsToParse[index].IndexOf(':'); + if (separatorIndex < 0) + { + FormatException e = + new( + StringUtil.Format(NavigationResources.ParsePathFormatError, pathsToParse[index])); + WriteError( + new ErrorRecord( + e, + "ParsePathFormatError", // RENAME + ErrorCategory.InvalidArgument, + pathsToParse[index])); + continue; + } + else + { + // Check to see if it is provider or drive qualified - if (separatorIndex < 0) - { - FormatException e = - new( - StringUtil.Format(NavigationResources.ParsePathFormatError, pathsToParse[index])); - WriteError( - new ErrorRecord( - e, - "ParsePathFormatError", // RENAME - ErrorCategory.InvalidArgument, - pathsToParse[index])); - continue; - } - else + if (SessionState.Path.IsProviderQualified(pathsToParse[index])) { - // Check to see if it is provider or drive qualified - - if (SessionState.Path.IsProviderQualified(pathsToParse[index])) - { - // The plus 2 is for the length of the provider separator - // which is "::" - - result = - pathsToParse[index].Substring( - 0, - separatorIndex + 2); - } - else - { - result = - pathsToParse[index].Substring( - 0, - separatorIndex + 1); - } - } + // The plus 2 is for the length of the provider separator + // which is "::" - break; - - case parentSet: - case literalPathSet: - try - { result = - SessionState.Path.ParseParent( - pathsToParse[index], - string.Empty, - CmdletProviderContext, - true); + pathsToParse[index].Substring( + 0, + separatorIndex + 2); } - catch (PSNotSupportedException) - { - // Since getting the parent path is not supported, - // the provider must be a container, item, or drive - // provider. Since the paths for these types of - // providers can't be split, asking for the parent - // is asking for an empty string. - result = string.Empty; - } - - break; - - case leafSet: - case leafBaseSet: - case extensionSet: - try + else { - // default handles leafSet result = - SessionState.Path.ParseChildName( - pathsToParse[index], - CmdletProviderContext, - true); - if (LeafBase) - { - result = System.IO.Path.GetFileNameWithoutExtension(result); - } - else if (Extension) - { - result = System.IO.Path.GetExtension(result); - } + pathsToParse[index].Substring( + 0, + separatorIndex + 1); } - catch (PSNotSupportedException) + } + } + else if (Leaf || LeafBase || Extension) + { + try + { + result = + SessionState.Path.ParseChildName( + pathsToParse[index], + CmdletProviderContext, + true); + if (LeafBase) { - // Since getting the leaf part of a path is not supported, - // the provider must be a container, item, or drive - // provider. Since the paths for these types of - // providers can't be split, asking for the leaf - // is asking for the specified path back. - result = pathsToParse[index]; + result = System.IO.Path.GetFileNameWithoutExtension(result); } - catch (DriveNotFoundException driveNotFound) + else if (Extension) { - WriteError( - new ErrorRecord( - driveNotFound.ErrorRecord, - driveNotFound)); - continue; - } - catch (ProviderNotFoundException providerNotFound) - { - WriteError( - new ErrorRecord( - providerNotFound.ErrorRecord, - providerNotFound)); - continue; + result = System.IO.Path.GetExtension(result); } - - break; - - case noQualifierSet: - result = RemoveQualifier(pathsToParse[index]); - break; - - default: - Dbg.Diagnostics.Assert( - false, - "Only a known parameter set should be called"); - break; + } + catch (PSNotSupportedException) + { + // Since getting the leaf part of a path is not supported, + // the provider must be a container, item, or drive + // provider. Since the paths for these types of + // providers can't be split, asking for the leaf + // is asking for the specified path back. + result = pathsToParse[index]; + } + catch (DriveNotFoundException driveNotFound) + { + WriteError( + new ErrorRecord( + driveNotFound.ErrorRecord, + driveNotFound)); + continue; + } + catch (ProviderNotFoundException providerNotFound) + { + WriteError( + new ErrorRecord( + providerNotFound.ErrorRecord, + providerNotFound)); + continue; + } + } + else if (NoQualifier) + { + result = RemoveQualifier(pathsToParse[index]); + } + else + { + // None of the switch parameters are true: default to -Parent behavior + try + { + result = + SessionState.Path.ParseParent( + pathsToParse[index], + string.Empty, + CmdletProviderContext, + true); + } + catch (PSNotSupportedException) + { + // Since getting the parent path is not supported, + // the provider must be a container, item, or drive + // provider. Since the paths for these types of + // providers can't be split, asking for the parent + // is asking for an empty string. + result = string.Empty; + } } if (result != null) diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs index 56efe65b4bb..91efda12263 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs @@ -1903,6 +1903,7 @@ protected override void BeginProcessing() } catch (CommandNotFoundException) { + // codeql[cs/microsoft/command-line-injection-shell-execution] - This is expected Poweshell behavior where user inputted paths are supported for the context of this method. The user assumes trust for the file path they are specifying and the process is on the user's system except for remoting in which case restricted remoting security guidelines should be used. startInfo.FileName = FilePath; #if UNIX // Arguments are passed incorrectly to the executable used for ShellExecute and not to filename https://github.com/dotnet/corefx/issues/30718 @@ -2187,15 +2188,12 @@ protected override void BeginProcessing() #region IDisposable Overrides /// <summary> - /// Dispose WaitHandle used to honor -Wait parameter. + /// Release all resources. /// </summary> + /// <remarks> + /// Dispose WaitHandle used to honor -Wait parameter. + /// </remarks> public void Dispose() - { - Dispose(true); - System.GC.SuppressFinalize(this); - } - - private void Dispose(bool isDisposing) { _cancellationTokenSource.Dispose(); } @@ -2840,7 +2838,7 @@ internal struct PROCESS_INFORMATION } [StructLayout(LayoutKind.Sequential)] - internal class SECURITY_ATTRIBUTES + internal sealed class SECURITY_ATTRIBUTES { public int nLength; public SafeLocalMemHandle lpSecurityDescriptor; @@ -2878,7 +2876,7 @@ protected override bool ReleaseHandle() } [StructLayout(LayoutKind.Sequential)] - internal class STARTUPINFO + internal sealed class STARTUPINFO { public int cb; public IntPtr lpReserved; diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/ResolvePathCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/ResolvePathCommand.cs index 3d1b66933d2..d0f5fecf495 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/ResolvePathCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/ResolvePathCommand.cs @@ -81,7 +81,7 @@ public SwitchParameter Relative /// </summary> [Parameter] public string RelativeBasePath - { + { get { return _relativeBasePath; diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/RollbackTransactionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/RollbackTransactionCommand.cs index 297dd5269a9..3d6fc27cc3c 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/RollbackTransactionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/RollbackTransactionCommand.cs @@ -28,4 +28,3 @@ protected override void EndProcessing() } } } - diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs index 739baa15bab..a00f9583d6a 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs @@ -674,13 +674,30 @@ protected override void ProcessRecord() #endregion Overrides #nullable enable + + /// <summary> + /// Writes a verbose message when a service property query fails. + /// </summary> + /// <param name="serviceName">Name of the service.</param> + /// <param name="propertyName">Name of the property that failed to be queried.</param> + private void WriteServicePropertyError(string serviceName, string propertyName) + { + Win32Exception e = new(Marshal.GetLastWin32Error()); + WriteVerbose( + StringUtil.Format( + ServiceResources.CouldNotGetServiceProperty, + serviceName, + propertyName, + e.Message)); + } + /// <summary> /// Adds UserName, Description, BinaryPathName, DelayedAutoStart and StartupType to a ServiceController object. /// </summary> /// <param name="scManagerHandle">Handle to the local SCManager instance.</param> /// <param name="service"></param> /// <returns>ServiceController as PSObject with UserName, Description and StartupType added.</returns> - private static PSObject AddProperties(nint scManagerHandle, ServiceController service) + private PSObject AddProperties(nint scManagerHandle, ServiceController service) { NakedWin32Handle hService = nint.Zero; @@ -709,6 +726,10 @@ private static PSObject AddProperties(nint scManagerHandle, ServiceController se { description = descriptionInfo.lpDescription; } + else + { + WriteServicePropertyError(service.ServiceName, nameof(NativeMethods.SERVICE_DESCRIPTIONW)); + } if (NativeMethods.QueryServiceConfig2( hService, @@ -717,6 +738,10 @@ private static PSObject AddProperties(nint scManagerHandle, ServiceController se { isDelayedAutoStart = autostartInfo.fDelayedAutostart; } + else + { + WriteServicePropertyError(service.ServiceName, nameof(NativeMethods.SERVICE_DELAYED_AUTO_START_INFO)); + } if (NativeMethods.QueryServiceConfig( hService, @@ -731,6 +756,15 @@ private static PSObject AddProperties(nint scManagerHandle, ServiceController se isDelayedAutoStart.Value); } } + else + { + WriteServicePropertyError(service.ServiceName, nameof(NativeMethods.QUERY_SERVICE_CONFIG)); + } + } + else + { + // handle when OpenServiceW itself fails: + WriteServicePropertyError(service.ServiceName, nameof(NativeMethods.SERVICE_QUERY_CONFIG)); } } finally diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/StartTransactionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/StartTransactionCommand.cs index 6c178626d96..58dcfbd1daa 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/StartTransactionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/StartTransactionCommand.cs @@ -107,4 +107,3 @@ protected override void EndProcessing() } } } - diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs index 17c9ab9a5d3..2a4a453f8f7 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestConnectionCommand.cs @@ -242,14 +242,13 @@ public class TestConnectionCommand : PSCmdlet, IDisposable /// </summary> protected override void BeginProcessing() { - switch (ParameterSetName) + if (Repeat) { - case RepeatPingParameterSet: - Count = int.MaxValue; - break; - case TcpPortParameterSet: - SetCountForTcpTest(); - break; + Count = int.MaxValue; + } + else if (ParameterSetName == TcpPortParameterSet) + { + SetCountForTcpTest(); } } @@ -265,21 +264,22 @@ protected override void ProcessRecord() foreach (var targetName in TargetName) { - switch (ParameterSetName) + if (MtuSize) + { + ProcessMTUSize(targetName); + } + else if (Traceroute) + { + ProcessTraceroute(targetName); + } + else if (ParameterSetName == TcpPortParameterSet) + { + ProcessConnectionByTCPPort(targetName); + } + else { - case DefaultPingParameterSet: - case RepeatPingParameterSet: - ProcessPing(targetName); - break; - case MtuSizeDetectParameterSet: - ProcessMTUSize(targetName); - break; - case TraceRouteParameterSet: - ProcessTraceroute(targetName); - break; - case TcpPortParameterSet: - ProcessConnectionByTCPPort(targetName); - break; + // None of the switch parameters are true: handle default ping or -Repeat + ProcessPing(targetName); } } } @@ -298,7 +298,7 @@ protected override void StopProcessing() private void SetCountForTcpTest() { - if (Repeat.IsPresent) + if (Repeat) { Count = int.MaxValue; } diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestPathCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestPathCommand.cs index d8748b6ef9c..50765c0e0ae 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/TestPathCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TestPathCommand.cs @@ -200,7 +200,7 @@ protected override void ProcessRecord() { WriteObject(result); } - + continue; } diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/TimeZoneCommands.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/TimeZoneCommands.cs index 8fdb2c55acd..22a50e41176 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/TimeZoneCommands.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/TimeZoneCommands.cs @@ -54,7 +54,7 @@ protected override void ProcessRecord() // make sure we've got the latest time zone settings TimeZoneInfo.ClearCachedData(); - if (this.ParameterSetName.Equals("ListAvailable", StringComparison.OrdinalIgnoreCase)) + if (ListAvailable) { // output the list of all available time zones WriteObject(TimeZoneInfo.GetSystemTimeZones(), true); diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/UseTransactionCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/UseTransactionCommand.cs index 056cf60265b..67d3acee44a 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/UseTransactionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/UseTransactionCommand.cs @@ -91,4 +91,3 @@ protected override void EndProcessing() } } } - diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/WebServiceProxy.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/WebServiceProxy.cs index 3fc313fe222..3f91d597e57 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/WebServiceProxy.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/WebServiceProxy.cs @@ -475,7 +475,7 @@ private object InstantiateWebServiceProxy(Assembly assembly) break; } - if (proxyType != null) + if (proxyType != null) { break; } diff --git a/src/Microsoft.PowerShell.Commands.Management/resources/ServiceResources.resx b/src/Microsoft.PowerShell.Commands.Management/resources/ServiceResources.resx index e1213c3fb05..c19f753529f 100644 --- a/src/Microsoft.PowerShell.Commands.Management/resources/ServiceResources.resx +++ b/src/Microsoft.PowerShell.Commands.Management/resources/ServiceResources.resx @@ -1,17 +1,17 @@ <?xml version="1.0" encoding="utf-8"?> <root> - <!-- - Microsoft ResX Schema - + <!-- + Microsoft ResX Schema + Version 2.0 - - The primary goals of this format is to allow a simple XML format - that is mostly human readable. The generation and parsing of the - various data types are done through the TypeConverter classes + + The primary goals of this format is to allow a simple XML format + that is mostly human readable. The generation and parsing of the + various data types are done through the TypeConverter classes associated with the data types. - + Example: - + ... ado.net/XML headers & schema ... <resheader name="resmimetype">text/microsoft-resx</resheader> <resheader name="version">2.0</resheader> @@ -26,36 +26,36 @@ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> <comment>This is a comment</comment> </data> - - There are any number of "resheader" rows that contain simple + + There are any number of "resheader" rows that contain simple name/value pairs. - - Each data row contains a name, and value. The row also contains a - type or mimetype. Type corresponds to a .NET class that support - text/value conversion through the TypeConverter architecture. - Classes that don't support this are serialized and stored with the + + Each data row contains a name, and value. The row also contains a + type or mimetype. Type corresponds to a .NET class that support + text/value conversion through the TypeConverter architecture. + Classes that don't support this are serialized and stored with the mimetype set. - - The mimetype is used for serialized objects, and tells the - ResXResourceReader how to depersist the object. This is currently not + + The mimetype is used for serialized objects, and tells the + ResXResourceReader how to depersist the object. This is currently not extensible. For a given mimetype the value must be set accordingly: - - Note - application/x-microsoft.net.object.binary.base64 is the format - that the ResXResourceWriter will generate, however the reader can + + Note - application/x-microsoft.net.object.binary.base64 is the format + that the ResXResourceWriter will generate, however the reader can read any of the formats listed below. - + mimetype: application/x-microsoft.net.object.binary.base64 - value : The object must be serialized with + value : The object must be serialized with : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter : and then encoded with base64 encoding. - + mimetype: application/x-microsoft.net.object.soap.base64 - value : The object must be serialized with + value : The object must be serialized with : System.Runtime.Serialization.Formatters.Soap.SoapFormatter : and then encoded with base64 encoding. mimetype: application/x-microsoft.net.object.bytearray.base64 - value : The object must be serialized into a byte array + value : The object must be serialized into a byte array : using a System.ComponentModel.TypeConverter : and then encoded with base64 encoding. --> @@ -213,4 +213,7 @@ <data name="UnsupportedStartupType" xml:space="preserve"> <value>The startup type '{0}' is not supported by {1}.</value> </data> + <data name="CouldNotGetServiceProperty" xml:space="preserve"> + <value>Could not retrieve property '{1}' for service '{0}': {2}</value> + </data> </root> diff --git a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj index d76b31d7170..cc98893c5b6 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj +++ b/src/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj @@ -8,7 +8,7 @@ <ItemGroup> <ProjectReference Include="..\System.Management.Automation\System.Management.Automation.csproj" /> - <PackageReference Include="Markdig.Signed" Version="0.41.3" /> + <PackageReference Include="Markdig.Signed" Version="1.1.2" /> <PackageReference Include="Microsoft.PowerShell.MarkdownRender" Version="7.2.1" /> </ItemGroup> @@ -32,9 +32,8 @@ </ItemGroup> <ItemGroup> - <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" /> - <PackageReference Include="System.Threading.AccessControl" Version="10.0.0-preview.6.25358.103" /> - <PackageReference Include="System.Drawing.Common" Version="10.0.0-preview.6.25358.103" /> + <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="5.3.0" /> + <PackageReference Include="System.Drawing.Common" Version="11.0.0-preview.3.26207.106" /> <PackageReference Include="JsonSchema.Net" Version="7.4.0" /> </ItemGroup> diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertTo-Html.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertTo-Html.cs index 2c530a8ab93..41dd159a3ba 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertTo-Html.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ConvertTo-Html.cs @@ -321,7 +321,7 @@ internal static class ConvertHTMLParameterDefinitionKeys /// <summary> /// This allows for @{e='foo';label='bar';alignment='center';width='20'}. /// </summary> - internal class ConvertHTMLExpressionParameterDefinition : CommandParameterDefinition + internal sealed class ConvertHTMLExpressionParameterDefinition : CommandParameterDefinition { protected override void SetEntries() { diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Csv.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Csv.cs index 9590cd7c771..d0b9f91e7f7 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Csv.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Csv.cs @@ -8,7 +8,7 @@ namespace Microsoft.PowerShell.Commands /// <summary> /// This class is used to parse CSV text. /// </summary> - internal class CSVHelper + internal sealed class CSVHelper { internal CSVHelper(char delimiter) { diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CsvCommands.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CsvCommands.cs index 6297d9b1f38..cb455417531 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CsvCommands.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CsvCommands.cs @@ -45,17 +45,19 @@ public abstract class BaseCsvWritingCommand : PSCmdlet public abstract PSObject InputObject { get; set; } /// <summary> - /// IncludeTypeInformation : The #TYPE line should be generated. Default is false. Cannot specify with NoTypeInformation. + /// IncludeTypeInformation : The #TYPE line should be generated. Default is false. /// </summary> [Parameter] [Alias("ITI")] public SwitchParameter IncludeTypeInformation { get; set; } /// <summary> - /// NoTypeInformation : The #TYPE line should not be generated. Default is true. Cannot specify with IncludeTypeInformation. + /// Gets or sets a value indicating whether to suppress the #TYPE line. + /// This parameter is obsolete and has no effect. It is retained for backward compatibility only. /// </summary> [Parameter(DontShow = true)] [Alias("NTI")] + [Obsolete("This parameter is obsolete and has no effect. The default behavior is to not include type information. Use -IncludeTypeInformation to include type information.")] public SwitchParameter NoTypeInformation { get; set; } = true; /// <summary> @@ -120,18 +122,6 @@ protected override void BeginProcessing() this.ThrowTerminatingError(errorRecord); } - if (this.MyInvocation.BoundParameters.ContainsKey(nameof(IncludeTypeInformation)) && this.MyInvocation.BoundParameters.ContainsKey(nameof(NoTypeInformation))) - { - InvalidOperationException exception = new(CsvCommandStrings.CannotSpecifyIncludeTypeInformationAndNoTypeInformation); - ErrorRecord errorRecord = new(exception, "CannotSpecifyIncludeTypeInformationAndNoTypeInformation", ErrorCategory.InvalidData, null); - this.ThrowTerminatingError(errorRecord); - } - - if (this.MyInvocation.BoundParameters.ContainsKey(nameof(IncludeTypeInformation))) - { - NoTypeInformation = !IncludeTypeInformation; - } - Delimiter = ImportExportCSVHelper.SetDelimiter(this, ParameterSetName, Delimiter, UseCulture); } } @@ -270,6 +260,14 @@ protected override void BeginProcessing() this.ThrowTerminatingError(errorRecord); } + // Validate that Append and NoHeader are not specified together. + if (Append && NoHeader) + { + InvalidOperationException exception = new(CsvCommandStrings.CannotSpecifyAppendAndNoHeader); + ErrorRecord errorRecord = new(exception, "CannotSpecifyBothAppendAndNoHeader", ErrorCategory.InvalidData, null); + this.ThrowTerminatingError(errorRecord); + } + _shouldProcess = ShouldProcess(Path); if (!_shouldProcess) { @@ -309,7 +307,7 @@ protected override void ProcessRecord() // write headers (row1: typename + row2: column names) if (!_isActuallyAppending && !NoHeader.IsPresent) { - if (NoTypeInformation == false) + if (IncludeTypeInformation) { WriteCsvLine(ExportCsvHelper.GetTypeString(InputObject)); } @@ -734,7 +732,7 @@ protected override void ProcessRecord() if (!NoHeader.IsPresent) { - if (NoTypeInformation == false) + if (IncludeTypeInformation) { WriteCsvLine(ExportCsvHelper.GetTypeString(InputObject)); } @@ -885,7 +883,7 @@ protected override void ProcessRecord() /// <summary> /// Helper class for Export-Csv and ConvertTo-Csv. /// </summary> - internal class ExportCsvHelper : IDisposable + internal sealed class ExportCsvHelper : IDisposable { private readonly char _delimiter; private readonly BaseCsvWritingCommand.QuoteKind _quoteKind; @@ -959,7 +957,7 @@ internal static IList<string> BuildPropertyNames(PSObject source, IList<string> /// <returns>Converted string.</returns> internal string ConvertPropertyNamesCSV(IList<string> propertyNames) { - ArgumentNullException.ThrowIfNull(propertyNames); + ArgumentNullException.ThrowIfNull(propertyNames); _outputString.Clear(); bool first = true; @@ -994,7 +992,7 @@ internal string ConvertPropertyNamesCSV(IList<string> propertyNames) AppendStringWithEscapeAlways(_outputString, propertyName); break; case BaseCsvWritingCommand.QuoteKind.AsNeeded: - + if (propertyName.AsSpan().IndexOfAny(_delimiter, '\n', '"') != -1) { AppendStringWithEscapeAlways(_outputString, propertyName); @@ -1023,7 +1021,7 @@ internal string ConvertPropertyNamesCSV(IList<string> propertyNames) /// <returns></returns> internal string ConvertPSObjectToCSV(PSObject mshObject, IList<string> propertyNames) { - ArgumentNullException.ThrowIfNull(propertyNames); + ArgumentNullException.ThrowIfNull(propertyNames); _outputString.Clear(); bool first = true; @@ -1044,7 +1042,7 @@ internal string ConvertPSObjectToCSV(PSObject mshObject, IList<string> propertyN { if (dictionary.Contains(propertyName)) { - value = dictionary[propertyName].ToString(); + value = dictionary[propertyName]?.ToString(); } else if (mshObject.Properties[propertyName] is PSPropertyInfo property) { @@ -1109,7 +1107,7 @@ internal string ConvertPSObjectToCSV(PSObject mshObject, IList<string> propertyN /// <returns>ToString() value.</returns> internal static string GetToStringValueForProperty(PSPropertyInfo property) { - ArgumentNullException.ThrowIfNull(property); + ArgumentNullException.ThrowIfNull(property); string value = null; try @@ -1224,7 +1222,7 @@ public void Dispose() /// <summary> /// Helper class to import single CSV file. /// </summary> - internal class ImportCsvHelper + internal sealed class ImportCsvHelper { #region constructor @@ -1271,7 +1269,7 @@ internal class ImportCsvHelper internal ImportCsvHelper(PSCmdlet cmdlet, char delimiter, IList<string> header, string typeName, StreamReader streamReader) { - ArgumentNullException.ThrowIfNull(cmdlet); + ArgumentNullException.ThrowIfNull(cmdlet); ArgumentNullException.ThrowIfNull(streamReader); _cmdlet = cmdlet; diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CustomSerialization.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CustomSerialization.cs index da4e4a1262a..6d224d29007 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CustomSerialization.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/CustomSerialization.cs @@ -14,7 +14,7 @@ namespace System.Management.Automation /// <summary> /// This class provides functionality for serializing a PSObject. /// </summary> - internal class CustomSerialization + internal sealed class CustomSerialization { #region constructor /// <summary> @@ -179,8 +179,7 @@ internal void Stop() /// <summary> /// This internal helper class provides methods for serializing mshObject. /// </summary> - internal class - CustomInternalSerializer + internal sealed class CustomInternalSerializer { #region constructor @@ -705,7 +704,7 @@ private void WriteMemberInfoCollection( continue; } - if (!(info is PSPropertyInfo property)) + if (info is not PSPropertyInfo property) { continue; } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs index f5d84dfd195..756afff13de 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/DebugRunspaceCommand.cs @@ -265,6 +265,15 @@ private void WaitAndReceiveRunspaceOutput() // Set up host script debugger to debug the runspace. _debugger.DebugRunspace(_runspace, breakAll: BreakAll); + _runspace.IsRemoteDebuggerAttached = true; + _runspace.Events?.GenerateEvent( + PSEngineEvent.OnDebugAttach, + sender: null, + args: Array.Empty<object>(), + extraData: null, + processInCurrentThread: true, + waitForCompletionInCurrentThread: false); + while (_debugging) { // Wait for running script. @@ -307,6 +316,7 @@ private void WaitAndReceiveRunspaceOutput() { _runspace.AvailabilityChanged -= HandleRunspaceAvailabilityChanged; _debugger.NestedDebuggingCancelledEvent -= HandleDebuggerNestedDebuggingCancelledEvent; + _runspace.IsRemoteDebuggerAttached = false; _debugger.StopDebugRunspace(_runspace); _newRunningScriptEvent.Dispose(); } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/ColumnInfo.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/ColumnInfo.cs index a42462737ea..65efee78a28 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/ColumnInfo.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/ColumnInfo.cs @@ -48,7 +48,7 @@ internal Type GetValueType(PSObject liveObject, out object columnValue) /// <returns>The source string limited in the number of lines.</returns> internal static object LimitString(object src) { - if (!(src is string srcString)) + if (src is not string srcString) { return src; } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/ExpressionColumnInfo.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/ExpressionColumnInfo.cs index e905f5d64b6..4d0c8af875c 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/ExpressionColumnInfo.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/ExpressionColumnInfo.cs @@ -6,7 +6,7 @@ namespace Microsoft.PowerShell.Commands { - internal class ExpressionColumnInfo : ColumnInfo + internal sealed class ExpressionColumnInfo : ColumnInfo { private readonly PSPropertyExpression _expression; diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/HeaderInfo.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/HeaderInfo.cs index d811ce32303..4f4e84a1569 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/HeaderInfo.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/HeaderInfo.cs @@ -7,7 +7,7 @@ namespace Microsoft.PowerShell.Commands { - internal class HeaderInfo + internal sealed class HeaderInfo { private readonly List<ColumnInfo> _columns = new(); diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/OriginalColumnInfo.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/OriginalColumnInfo.cs index 974188c2f74..4ec5e2240fa 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/OriginalColumnInfo.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/OriginalColumnInfo.cs @@ -9,7 +9,7 @@ namespace Microsoft.PowerShell.Commands { - internal class OriginalColumnInfo : ColumnInfo + internal sealed class OriginalColumnInfo : ColumnInfo { private readonly string _liveObjectPropertyName; private readonly OutGridViewCommand _parentCmdlet; diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/OutGridViewCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/OutGridViewCommand.cs index 43e24e8b5b6..574ca39426d 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/OutGridViewCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/OutGridViewCommand.cs @@ -323,7 +323,7 @@ internal static GridHeader ConstructGridHeader(PSObject input, OutGridViewComman internal abstract void ProcessInputObject(PSObject input); } - internal class ScalarTypeHeader : GridHeader + internal sealed class ScalarTypeHeader : GridHeader { private readonly Type _originalScalarType; @@ -349,7 +349,7 @@ internal override void ProcessInputObject(PSObject input) } } - internal class NonscalarTypeHeader : GridHeader + internal sealed class NonscalarTypeHeader : GridHeader { private readonly AppliesTo _appliesTo = null; @@ -453,7 +453,7 @@ internal override void ProcessInputObject(PSObject input) } } - internal class HeteroTypeHeader : GridHeader + internal sealed class HeteroTypeHeader : GridHeader { internal HeteroTypeHeader(OutGridViewCommand parentCmd, PSObject input) : base(parentCmd) { diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/OutWindowProxy.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/OutWindowProxy.cs index b406c2bc78c..ef9b0528c75 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/OutWindowProxy.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/OutWindowProxy.cs @@ -13,7 +13,7 @@ namespace Microsoft.PowerShell.Commands { - internal class OutWindowProxy : IDisposable + internal sealed class OutWindowProxy : IDisposable { private const string OutGridViewWindowClassName = "Microsoft.Management.UI.Internal.OutGridViewWindow"; private const string OriginalTypePropertyName = "OriginalType"; diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/ScalarTypeColumnInfo.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/ScalarTypeColumnInfo.cs index 77f80c269a3..38cc9668856 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/ScalarTypeColumnInfo.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/ScalarTypeColumnInfo.cs @@ -6,7 +6,7 @@ namespace Microsoft.PowerShell.Commands { - internal class ScalarTypeColumnInfo : ColumnInfo + internal sealed class ScalarTypeColumnInfo : ColumnInfo { private readonly Type _type; @@ -29,7 +29,7 @@ internal override object GetValue(PSObject liveObject) } } - internal class TypeNameColumnInfo : ColumnInfo + internal sealed class TypeNameColumnInfo : ColumnInfo { internal TypeNameColumnInfo(string staleObjectPropertyName, string displayName) : base(staleObjectPropertyName, displayName) @@ -43,7 +43,7 @@ internal override object GetValue(PSObject liveObject) } } - internal class ToStringColumnInfo : ColumnInfo + internal sealed class ToStringColumnInfo : ColumnInfo { private readonly OutGridViewCommand _parentCmdlet; @@ -60,7 +60,7 @@ internal override object GetValue(PSObject liveObject) } } - internal class IndexColumnInfo : ColumnInfo + internal sealed class IndexColumnInfo : ColumnInfo { private int _index = 0; diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/TableView.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/TableView.cs index 9213484d332..e152bb7c973 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/TableView.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/OutGridView/TableView.cs @@ -12,7 +12,7 @@ namespace Microsoft.PowerShell.Commands { - internal class TableView + internal sealed class TableView { private PSPropertyExpressionFactory _expressionFactory; private TypeInfoDataBase _typeInfoDatabase; diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-object/Format-Object.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-object/Format-Object.cs index 2179243279d..693a799c809 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-object/Format-Object.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-object/Format-Object.cs @@ -32,6 +32,7 @@ public FormatCustomCommand() /// will be determined using property sets, etc. /// </summary> [Parameter(Position = 0)] + [ValidateNotNullOrEmpty] public object[] Property { get { return _props; } @@ -41,6 +42,12 @@ public object[] Property private object[] _props; + /// <summary> + /// Gets or sets the properties to exclude from formatting. + /// </summary> + [Parameter] + public string[] ExcludeProperty { get; set; } + /// <summary> /// </summary> /// <value></value> @@ -61,6 +68,18 @@ internal override FormattingCommandLineParameters GetCommandLineParameters() { FormattingCommandLineParameters parameters = new(); + // Check View conflicts first (before any auto-expansion) + if (!string.IsNullOrEmpty(this.View)) + { + // View cannot be used with Property or ExcludeProperty + if ((_props is not null && _props.Length != 0) || (ExcludeProperty is not null && ExcludeProperty.Length != 0)) + { + ReportCannotSpecifyViewAndProperty(); + } + + parameters.viewName = this.View; + } + if (_props != null) { ParameterProcessor processor = new(new FormatObjectParameterDefinition()); @@ -68,15 +87,17 @@ internal override FormattingCommandLineParameters GetCommandLineParameters() parameters.mshParameterList = processor.ProcessParameters(_props, invocationContext); } - if (!string.IsNullOrEmpty(this.View)) + if (ExcludeProperty is not null) { - // we have a view command line switch - if (parameters.mshParameterList.Count != 0) + parameters.excludePropertyFilter = new PSPropertyExpressionFilter(ExcludeProperty); + + // ExcludeProperty implies -Property * for better UX + if (_props is null || _props.Length == 0) { - ReportCannotSpecifyViewAndProperty(); + ParameterProcessor processor = new(new FormatObjectParameterDefinition()); + TerminatingErrorContext invocationContext = new(this); + parameters.mshParameterList = processor.ProcessParameters(new object[] { "*" }, invocationContext); } - - parameters.viewName = this.View; } parameters.groupByParameter = this.ProcessGroupByParameter(); diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-wide/Format-Wide.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-wide/Format-Wide.cs index aeddf498f44..c6aef5c20be 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-wide/Format-Wide.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-wide/Format-Wide.cs @@ -42,9 +42,14 @@ public object Property private object _prop; /// <summary> - /// Optional, non positional parameter. + /// Gets or sets the properties to exclude from formatting. + /// </summary> + [Parameter] + public string[] ExcludeProperty { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether to autosize the output. /// </summary> - /// <value></value> [Parameter] public SwitchParameter AutoSize { @@ -74,6 +79,18 @@ internal override FormattingCommandLineParameters GetCommandLineParameters() { FormattingCommandLineParameters parameters = new(); + // Check View conflicts first (before any auto-expansion) + if (!string.IsNullOrEmpty(this.View)) + { + // View cannot be used with Property or ExcludeProperty + if (_prop is not null || (ExcludeProperty is not null && ExcludeProperty.Length != 0)) + { + ReportCannotSpecifyViewAndProperty(); + } + + parameters.viewName = this.View; + } + if (_prop != null) { ParameterProcessor processor = new(new FormatWideParameterDefinition()); @@ -81,15 +98,17 @@ internal override FormattingCommandLineParameters GetCommandLineParameters() parameters.mshParameterList = processor.ProcessParameters(new object[] { _prop }, invocationContext); } - if (!string.IsNullOrEmpty(this.View)) + if (ExcludeProperty is not null) { - // we have a view command line switch - if (parameters.mshParameterList.Count != 0) + parameters.excludePropertyFilter = new PSPropertyExpressionFilter(ExcludeProperty); + + // ExcludeProperty implies -Property * for better UX + if (_prop is null) { - ReportCannotSpecifyViewAndProperty(); + ParameterProcessor processor = new(new FormatWideParameterDefinition()); + TerminatingErrorContext invocationContext = new(this); + parameters.mshParameterList = processor.ProcessParameters(new object[] { "*" }, invocationContext); } - - parameters.viewName = this.View; } // we cannot specify -column and -autosize, they are mutually exclusive diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Get-PSBreakpoint.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Get-PSBreakpoint.cs index 85bb8fa53b7..c675a0f6dc1 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Get-PSBreakpoint.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Get-PSBreakpoint.cs @@ -115,7 +115,7 @@ protected override void ProcessRecord() Command, (Breakpoint breakpoint, string command) => { - if (!(breakpoint is CommandBreakpoint commandBreakpoint)) + if (breakpoint is not CommandBreakpoint commandBreakpoint) { return false; } @@ -130,7 +130,7 @@ protected override void ProcessRecord() Variable, (Breakpoint breakpoint, string variable) => { - if (!(breakpoint is VariableBreakpoint variableBreakpoint)) + if (breakpoint is not VariableBreakpoint variableBreakpoint) { return false; } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetRandomCommandBase.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetRandomCommandBase.cs index 7d6e4e71e67..8e50b1cf5a9 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetRandomCommandBase.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetRandomCommandBase.cs @@ -488,7 +488,7 @@ protected override void ProcessRecord() { if (EffectiveParameterSet == MyParameterSet.RandomListItem) { - if (ParameterSetName == ShuffleParameterSet) + if (Shuffle) { // this allows for $null to be in an array passed to InputObject foreach (object item in InputObject ?? _nullInArray) @@ -561,7 +561,7 @@ protected override void EndProcessing() /// methods using the NextBytes() primitive based on the CLR implementation: /// https://referencesource.microsoft.com/#mscorlib/system/random.cs. /// </summary> - internal class PolymorphicRandomNumberGenerator + internal sealed class PolymorphicRandomNumberGenerator { /// <summary> /// Initializes a new instance of the <see cref="PolymorphicRandomNumberGenerator"/> class. diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetUptime.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetUptime.cs index e8dcfbe254a..c21165301e2 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetUptime.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetUptime.cs @@ -36,16 +36,15 @@ protected override void ProcessRecord() { TimeSpan uptime = TimeSpan.FromSeconds(Stopwatch.GetTimestamp() / Stopwatch.Frequency); - switch (ParameterSetName) + if (Since) { - case TimespanParameterSet: - // return TimeSpan of time since the system started up - WriteObject(uptime); - break; - case SinceParameterSet: - // return Datetime when the system started up - WriteObject(DateTime.Now.Subtract(uptime)); - break; + // Output the time of the last system boot. + WriteObject(DateTime.Now.Subtract(uptime)); + } + else + { + // Output the time elapsed since the last system boot. + WriteObject(uptime); } } else diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImplicitRemotingCommands.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImplicitRemotingCommands.cs index 8142f682b36..d01d144caca 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImplicitRemotingCommands.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImplicitRemotingCommands.cs @@ -1916,7 +1916,7 @@ internal List<string> GenerateProxyModule( #endregion } - internal class ImplicitRemotingCodeGenerator + internal sealed class ImplicitRemotingCodeGenerator { internal static readonly Version VersionOfScriptWriter = new(1, 0); @@ -2620,7 +2620,7 @@ private string GenerateConnectionStringForNewRunspace() private string GenerateAllowRedirectionParameter() { - if (!(_remoteRunspaceInfo.Runspace.ConnectionInfo is WSManConnectionInfo wsmanConnectionInfo)) + if (_remoteRunspaceInfo.Runspace.ConnectionInfo is not WSManConnectionInfo wsmanConnectionInfo) { return string.Empty; } @@ -2646,7 +2646,7 @@ private string GenerateAuthenticationMechanismParameter() return string.Empty; } - if (!(_remoteRunspaceInfo.Runspace.ConnectionInfo is WSManConnectionInfo wsmanConnectionInfo)) + if (_remoteRunspaceInfo.Runspace.ConnectionInfo is not WSManConnectionInfo wsmanConnectionInfo) { return string.Empty; } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Import-LocalizedData.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Import-LocalizedData.cs index 1f43670531d..c4e423ffd1d 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Import-LocalizedData.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Import-LocalizedData.cs @@ -158,7 +158,7 @@ protected override void ProcessRecord() ThrowTerminatingError( new ErrorRecord(nse, "CannotDefineSupportedCommand", ErrorCategory.PermissionDenied, null)); } - + SystemPolicy.LogWDACAuditMessage( context: Context, title: ImportLocalizedDataStrings.WDACLogTitle, diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/JsonSchemaReferenceResolutionException.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/JsonSchemaReferenceResolutionException.cs index ba664ccef1b..7c2a7ac65f4 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/JsonSchemaReferenceResolutionException.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/JsonSchemaReferenceResolutionException.cs @@ -11,7 +11,7 @@ namespace Microsoft.PowerShell.Commands; /// Thrown during evaluation of <see cref="TestJsonCommand"/> when an attempt /// to resolve a <code>$ref</code> or <code>$dynamicRef</code> fails. /// </summary> -internal class JsonSchemaReferenceResolutionException : Exception +internal sealed class JsonSchemaReferenceResolutionException : Exception { /// <summary> /// Initializes a new instance of the <see cref="JsonSchemaReferenceResolutionException"/> class. diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Measure-Object.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Measure-Object.cs index cf483b16a93..1e741758270 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Measure-Object.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Measure-Object.cs @@ -824,7 +824,7 @@ protected override void EndProcessing() string errorId = (IsMeasuringGeneric) ? "GenericMeasurePropertyNotFound" : "TextMeasurePropertyNotFound"; WritePropertyNotFoundError(propertyName, errorId); } - + continue; } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-Object.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-Object.cs index f2c31faf400..0ea312f2963 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-Object.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/New-Object.cs @@ -196,12 +196,12 @@ protected override void BeginProcessing() { ThrowTerminatingError( new ErrorRecord( - new PSNotSupportedException(NewObjectStrings.CannotCreateTypeConstrainedLanguage), + new PSNotSupportedException(NewObjectStrings.CannotCreateTypeConstrainedLanguage), "CannotCreateTypeConstrainedLanguage", ErrorCategory.PermissionDenied, targetObject: null)); } - + SystemPolicy.LogWDACAuditMessage( context: Context, title: NewObjectStrings.TypeWDACLogTitle, diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewGuidCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewGuidCommand.cs index 3aa070bcd62..0e466f86d3f 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewGuidCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/NewGuidCommand.cs @@ -45,11 +45,11 @@ protected override void ProcessRecord() { ErrorRecord error = new(ex, "StringNotRecognizedAsGuid", ErrorCategory.InvalidArgument, null); WriteError(error); - } + } } else { - guid = ParameterSetName is "Empty" ? Guid.Empty : Guid.NewGuid(); + guid = Empty.ToBool() ? Guid.Empty : Guid.NewGuid(); } WriteObject(guid); diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ObjectCommandComparer.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ObjectCommandComparer.cs index bb71dfdbe1c..13b0234a02b 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ObjectCommandComparer.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ObjectCommandComparer.cs @@ -19,7 +19,7 @@ namespace Microsoft.PowerShell.Commands /// isExistingProperty is needed to distinguish whether a property exists and its value is null or /// the property does not exist at all. /// </summary> - internal class ObjectCommandPropertyValue + internal sealed class ObjectCommandPropertyValue { private ObjectCommandPropertyValue() { } @@ -77,7 +77,7 @@ internal CultureInfo Culture /// <returns>True if both the objects are same or else returns false.</returns> public override bool Equals(object inputObject) { - if (!(inputObject is ObjectCommandPropertyValue objectCommandPropertyValueObject)) + if (inputObject is not ObjectCommandPropertyValue objectCommandPropertyValueObject) { return false; } @@ -136,7 +136,7 @@ public override int GetHashCode() /// <summary> /// ObjectCommandComparer class. /// </summary> - internal class ObjectCommandComparer : IComparer + internal sealed class ObjectCommandComparer : IComparer { /// <summary> /// Initializes a new instance of the <see cref="ObjectCommandComparer"/> class. diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/OrderObjectBase.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/OrderObjectBase.cs index 519b472620b..596bbcaafc6 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/OrderObjectBase.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/OrderObjectBase.cs @@ -24,7 +24,7 @@ internal static class SortObjectParameterDefinitionKeys /// <summary> /// </summary> - internal class SortObjectExpressionParameterDefinition : CommandParameterDefinition + internal sealed class SortObjectExpressionParameterDefinition : CommandParameterDefinition { protected override void SetEntries() { @@ -36,7 +36,7 @@ protected override void SetEntries() /// <summary> /// </summary> - internal class GroupObjectExpressionParameterDefinition : CommandParameterDefinition + internal sealed class GroupObjectExpressionParameterDefinition : CommandParameterDefinition { protected override void SetEntries() { @@ -358,7 +358,7 @@ internal static string[] GetDefaultKeyPropertySet(PSObject mshObj) return null; } - if (!(standardNames.Members["DefaultKeyPropertySet"] is PSPropertySet defaultKeys)) + if (standardNames.Members["DefaultKeyPropertySet"] is not PSPropertySet defaultKeys) { return null; } @@ -630,7 +630,7 @@ internal sealed class OrderByPropertyEntry internal bool comparable = false; } - internal class OrderByPropertyComparer : IComparer<OrderByPropertyEntry> + internal sealed class OrderByPropertyComparer : IComparer<OrderByPropertyEntry> { internal OrderByPropertyComparer(bool[] ascending, CultureInfo cultureInfo, bool caseSensitive) { @@ -699,7 +699,7 @@ internal static OrderByPropertyComparer CreateComparer(List<OrderByPropertyEntry private readonly ObjectCommandComparer[] _propertyComparers = null; } - internal class IndexedOrderByPropertyComparer : IComparer<OrderByPropertyEntry> + internal sealed class IndexedOrderByPropertyComparer : IComparer<OrderByPropertyEntry> { internal IndexedOrderByPropertyComparer(OrderByPropertyComparer orderByPropertyComparer) { diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Select-Object.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Select-Object.cs index efd7007e131..e0b0bff07c5 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Select-Object.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Select-Object.cs @@ -12,48 +12,7 @@ namespace Microsoft.PowerShell.Commands { - /// <summary> - /// Helper class to do wildcard matching on PSPropertyExpressions. - /// </summary> - internal sealed class PSPropertyExpressionFilter - { - /// <summary> - /// Initializes a new instance of the <see cref="PSPropertyExpressionFilter"/> class - /// with the specified array of patterns. - /// </summary> - /// <param name="wildcardPatternsStrings">Array of pattern strings to use.</param> - internal PSPropertyExpressionFilter(string[] wildcardPatternsStrings) - { - ArgumentNullException.ThrowIfNull(wildcardPatternsStrings); - - _wildcardPatterns = new WildcardPattern[wildcardPatternsStrings.Length]; - for (int k = 0; k < wildcardPatternsStrings.Length; k++) - { - _wildcardPatterns[k] = WildcardPattern.Get(wildcardPatternsStrings[k], WildcardOptions.IgnoreCase); - } - } - - /// <summary> - /// Try to match the expression against the array of wildcard patterns. - /// The first match shortcircuits the search. - /// </summary> - /// <param name="expression">PSPropertyExpression to test against.</param> - /// <returns>True if there is a match, else false.</returns> - internal bool IsMatch(PSPropertyExpression expression) - { - for (int k = 0; k < _wildcardPatterns.Length; k++) - { - if (_wildcardPatterns[k].IsMatch(expression.ToString())) - return true; - } - - return false; - } - - private readonly WildcardPattern[] _wildcardPatterns; - } - - internal class SelectObjectExpressionParameterDefinition : CommandParameterDefinition + internal sealed class SelectObjectExpressionParameterDefinition : CommandParameterDefinition { protected override void SetEntries() { @@ -866,7 +825,7 @@ protected override void EndProcessing() [SuppressMessage("Microsoft.Usage", "CA2237:MarkISerializableTypesWithSerializable", Justification = "This exception is internal and never thrown by any public API")] [SuppressMessage("Microsoft.Design", "CA1032:ImplementStandardExceptionConstructors", Justification = "This exception is internal and never thrown by any public API")] [SuppressMessage("Microsoft.Design", "CA1064:ExceptionsShouldBePublic", Justification = "This exception is internal and never thrown by any public API")] - internal class SelectObjectException : SystemException + internal sealed class SelectObjectException : SystemException { internal ErrorRecord ErrorRecord { get; } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandProxy.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandProxy.cs index a8a9c8f194d..ab8909bb590 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandProxy.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ShowCommand/ShowCommandProxy.cs @@ -16,7 +16,7 @@ namespace Microsoft.PowerShell.Commands /// Help show-command create WPF object and invoke WPF windows with the /// Microsoft.PowerShell.Commands.ShowCommandInternal.ShowCommandHelperhelp type defined in Microsoft.PowerShell.GraphicalHost.dll. /// </summary> - internal class ShowCommandProxy + internal sealed class ShowCommandProxy { private const string ShowCommandHelperName = "Microsoft.PowerShell.Commands.ShowCommandInternal.ShowCommandHelper"; diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/StartSleepCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/StartSleepCommand.cs index df1c8b76fb8..839a0b7c051 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/StartSleepCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/StartSleepCommand.cs @@ -110,7 +110,7 @@ protected override void ProcessRecord() case "Milliseconds": sleepTime = Milliseconds; break; - + case "FromTimeSpan": if (Duration.TotalMilliseconds > int.MaxValue) { diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Tee-Object.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Tee-Object.cs index 27ea15406f7..4a0f4831299 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Tee-Object.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Tee-Object.cs @@ -140,12 +140,15 @@ protected override void EndProcessing() _commandWrapper.ShutDown(); } - private void Dispose(bool isDisposing) + /// <summary> + /// Release all resources. + /// </summary> + public void Dispose() { if (!_alreadyDisposed) { _alreadyDisposed = true; - if (isDisposing && _commandWrapper != null) + if (_commandWrapper != null) { _commandWrapper.Dispose(); _commandWrapper = null; @@ -153,15 +156,6 @@ private void Dispose(bool isDisposing) } } - /// <summary> - /// Dispose method in IDisposable. - /// </summary> - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - #region private private CommandWrapper _commandWrapper; private bool _alreadyDisposed; diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/TestJsonCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/TestJsonCommand.cs index 32603f160f8..909cbff3c8f 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/TestJsonCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/TestJsonCommand.cs @@ -264,19 +264,11 @@ protected override void ProcessRecord() if (_jschema != null) { - EvaluationResults evaluationResults = _jschema.Evaluate(parsedJson, new EvaluationOptions { OutputFormat = OutputFormat.List }); + EvaluationResults evaluationResults = _jschema.Evaluate(parsedJson, new EvaluationOptions { OutputFormat = OutputFormat.Hierarchical }); result = evaluationResults.IsValid; if (!result) { - HandleValidationErrors(evaluationResults); - - if (evaluationResults.HasDetails) - { - foreach (var nestedResult in evaluationResults.Details) - { - HandleValidationErrors(nestedResult); - } - } + ReportValidationErrors(evaluationResults); } } } @@ -298,6 +290,33 @@ protected override void ProcessRecord() WriteObject(result); } + /// <summary> + /// Recursively reports validation errors from hierarchical evaluation results. + /// Skips nodes (and their children) where IsValid is true to avoid false positives + /// from constructs like OneOf or AnyOf. + /// </summary> + /// <param name="evaluationResult">The evaluation result to process.</param> + private void ReportValidationErrors(EvaluationResults evaluationResult) + { + // Skip this node and all children if validation passed + if (evaluationResult.IsValid) + { + return; + } + + // Report errors at this level + HandleValidationErrors(evaluationResult); + + // Recursively process child results + if (evaluationResult.HasDetails) + { + foreach (var nestedResult in evaluationResult.Details) + { + ReportValidationErrors(nestedResult); + } + } + } + private void HandleValidationErrors(EvaluationResults evaluationResult) { if (!evaluationResult.HasErrors) diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Var.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Var.cs index 12cfb784c72..a41b284f568 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Var.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Var.cs @@ -697,7 +697,6 @@ public SwitchParameter PassThru /// Gets whether we will append to the variable if it exists. /// </summary> [Parameter] - [Experimental(ExperimentalFeature.PSRedirectToVariable, ExperimentAction.Show)] public SwitchParameter Append { get; set; } private bool _nameIsFormalParameter; diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/BasicHtmlWebResponseObject.Common.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/BasicHtmlWebResponseObject.Common.cs index 9bd76f99413..ea650e80e67 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/BasicHtmlWebResponseObject.Common.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/BasicHtmlWebResponseObject.Common.cs @@ -81,9 +81,9 @@ public WebCmdletElementCollection InputFields { List<PSObject> parsedFields = new(); MatchCollection fieldMatch = HtmlParser.InputFieldRegex.Matches(Content); - foreach (Match field in fieldMatch) + foreach (Match match in fieldMatch) { - parsedFields.Add(CreateHtmlObject(field.Value, "INPUT")); + parsedFields.Add(CreateHtmlObject(match.Value, "INPUT")); } _inputFields = new WebCmdletElementCollection(parsedFields); diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/HttpVersionCompletionsAttribute.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/HttpVersionCompletionsAttribute.cs index 05eb8ed96b6..903ff4d8f80 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/HttpVersionCompletionsAttribute.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/HttpVersionCompletionsAttribute.cs @@ -46,6 +46,6 @@ static HttpVersionCompletionsAttribute() /// <inheritdoc/> public HttpVersionCompletionsAttribute() : base(AllowedVersions) { - } + } } } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/InvokeRestMethodCommand.Common.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/InvokeRestMethodCommand.Common.cs index eafdaadbede..22ffaef288c 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/InvokeRestMethodCommand.Common.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/InvokeRestMethodCommand.Common.cs @@ -354,7 +354,7 @@ public enum RestReturnType Xml, } - internal class BufferingStreamReader : Stream + internal sealed class BufferingStreamReader : Stream { internal BufferingStreamReader(Stream baseStream, TimeSpan perReadTimeout, CancellationToken cancellationToken) { diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs index 3b8ff2e3372..f1a455974b9 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/WebRequestPSCmdlet.Common.cs @@ -1295,6 +1295,7 @@ internal virtual HttpResponseMessage GetResponse(HttpClient client, HttpRequestM WriteWebRequestDebugInfo(currentRequest); } + // codeql[cs/ssrf] - This is expected Poweshell behavior where user inputted Uri is supported for the context of this method. The user assumes trust for the Uri and invocation is done on the user's machine, not a web application. If there is concern for remoting, they should use restricted remoting. response = client.SendAsync(currentRequest, HttpCompletionOption.ResponseHeadersRead, _cancelToken.Token).GetAwaiter().GetResult(); if (IsWriteVerboseEnabled()) @@ -1994,7 +1995,7 @@ private static StreamContent GetMultipartStreamContent(object fieldName, Stream /// <param name="file">The file to use for the <see cref="StreamContent"/></param> private static StreamContent GetMultipartFileContent(object fieldName, FileInfo file) { - StreamContent result = GetMultipartStreamContent(fieldName: fieldName, stream: new FileStream(file.FullName, FileMode.Open)); + StreamContent result = GetMultipartStreamContent(fieldName: fieldName, stream: new FileStream(file.FullName, FileMode.Open, FileAccess.Read, FileShare.Read)); result.Headers.ContentDisposition.FileName = file.Name; result.Headers.ContentDisposition.FileNameStar = file.Name; diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommand.cs index eb8c3e3cc08..173d999b06d 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/ConvertToJsonCommand.cs @@ -98,7 +98,7 @@ protected virtual void Dispose(bool disposing) _cancellationSource.Dispose(); } } - + private readonly List<object> _inputObjects = new(); /// <summary> diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebProxy.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebProxy.cs index 6e9f6bdf98e..c8fed859771 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebProxy.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/CoreCLR/WebProxy.cs @@ -8,7 +8,7 @@ namespace Microsoft.PowerShell.Commands { - internal class WebProxy : IWebProxy, IEquatable<WebProxy> + internal sealed class WebProxy : IWebProxy, IEquatable<WebProxy> { private ICredentials? _credentials; private readonly Uri _proxyAddress; @@ -31,7 +31,7 @@ public bool Equals(WebProxy? other) return false; } - // _proxyAddress cannot be null as it is set in the constructor + // _proxyAddress cannot be null as it is set in the constructor return other._credentials == _credentials && _proxyAddress.Equals(other._proxyAddress) && BypassProxyOnLocal == other.BypassProxyOnLocal; diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs index 33465683153..6506f2bd2ce 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs @@ -10,7 +10,6 @@ using System.Management.Automation.Language; using System.Numerics; using System.Reflection; -using System.Text.RegularExpressions; using System.Threading; using Newtonsoft.Json; @@ -289,11 +288,11 @@ private static PSObject PopulateFromJDictionary(JObject entries, DuplicateMember return null; } - // Array switch (entry.Value) { case JArray list: { + // Array var listResult = PopulateFromJArray(list, out error); if (error != null) { @@ -333,45 +332,44 @@ private static ICollection<object> PopulateFromJArray(JArray list, out ErrorReco { error = null; var result = new object[list.Count]; + var i = 0; - for (var index = 0; index < list.Count; index++) + foreach (var element in list) { - var element = list[index]; switch (element) { case JArray subList: + // Array + result[i++] = PopulateFromJArray(subList, out error); + if (error != null) { - // Array - var listResult = PopulateFromJArray(subList, out error); - if (error != null) - { - return null; - } - - result[index] = listResult; - break; + return null; } + + break; + case JObject dic: + // Dictionary + result[i++] = PopulateFromJDictionary(dic, new DuplicateMemberHashSet(dic.Count), out error); + if (error != null) { - // Dictionary - var dicResult = PopulateFromJDictionary(dic, new DuplicateMemberHashSet(dic.Count), out error); - if (error != null) - { - return null; - } - - result[index] = dicResult; - break; + return null; } + + break; + case JValue value: + if (value.Type != JTokenType.Comment) { - result[index] = value.Value; - break; + result[i++] = value.Value; } + + break; } } - return result; + // In the common case of not having any comments, return the original array, otherwise create a sliced copy. + return i == list.Count ? result : result[..i]; } // This function is a clone of PopulateFromDictionary using JObject as an input. @@ -436,46 +434,44 @@ private static ICollection<object> PopulateHashTableFromJArray(JArray list, out { error = null; var result = new object[list.Count]; + var i = 0; - for (var index = 0; index < list.Count; index++) + foreach (var element in list) { - var element = list[index]; - switch (element) { - case JArray array: + case JArray subList: + // Array + result[i++] = PopulateHashTableFromJArray(subList, out error); + if (error != null) { - // Array - var listResult = PopulateHashTableFromJArray(array, out error); - if (error != null) - { - return null; - } - - result[index] = listResult; - break; + return null; } + + break; + case JObject dic: + // Dictionary + result[i++] = PopulateHashTableFromJDictionary(dic, out error); + if (error != null) { - // Dictionary - var dicResult = PopulateHashTableFromJDictionary(dic, out error); - if (error != null) - { - return null; - } - - result[index] = dicResult; - break; + return null; } + + break; + case JValue value: + if (value.Type != JTokenType.Comment) { - result[index] = value.Value; - break; + result[i++] = value.Value; } + + break; } } - return result; + // In the common case of not having any comments, return the original array, otherwise create a sliced copy. + return i == list.Count ? result : result[..i]; } #endregion ConvertFromJson @@ -666,7 +662,7 @@ private static object ProcessValue(object obj, int currentDepth, in ConvertToJso /// </returns> private static object AddPsProperties(object psObj, object obj, int depth, bool isPurePSObj, bool isCustomObj, in ConvertToJsonContext context) { - if (!(psObj is PSObject pso)) + if (psObj is not PSObject pso) { return obj; } diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs index 52ff515b0b2..d24961834b6 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/StreamHelper.cs @@ -22,7 +22,7 @@ namespace Microsoft.PowerShell.Commands /// this class as a wrapper to MemoryStream to lazily initialize. Otherwise, the /// content will unnecessarily be read even if there are no consumers for it. /// </summary> - internal class WebResponseContentMemoryStream : MemoryStream + internal sealed class WebResponseContentMemoryStream : MemoryStream { #region Data diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/XmlCommands.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/XmlCommands.cs index 6157849eeec..cc3b1f2b251 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/XmlCommands.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/XmlCommands.cs @@ -331,13 +331,12 @@ public string[] LiteralPath private bool _disposed = false; /// <summary> - /// Public dispose method. + /// Release all resources. /// </summary> public void Dispose() { if (!_disposed) { - GC.SuppressFinalize(this); if (_helper != null) { _helper.Dispose(); @@ -715,7 +714,7 @@ protected override void ProcessRecord() /// <summary> /// Helper class to import single XML file. /// </summary> - internal class ImportXmlHelper : IDisposable + internal sealed class ImportXmlHelper : IDisposable { #region constructor diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/MshHostTraceListener.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/MshHostTraceListener.cs index 357e0222b6d..b7e4ed279aa 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/MshHostTraceListener.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/MshHostTraceListener.cs @@ -17,7 +17,7 @@ namespace Microsoft.PowerShell.Commands /// This trace listener cannot be specified in the app.config file. /// It must be added through the add-tracelistener cmdlet. /// </remarks> - internal class PSHostTraceListener + internal sealed class PSHostTraceListener : System.Diagnostics.TraceListener { #region TraceListener constructors and disposer diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/TraceExpressionCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/TraceExpressionCommand.cs index 91cb0d46abc..1eadd4934b3 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/TraceExpressionCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/trace/TraceExpressionCommand.cs @@ -323,14 +323,14 @@ public void Dispose() /// cmdlet. It gets attached to the sub-pipelines success or error pipeline and redirects /// all objects written to these pipelines to trace-command pipeline. /// </summary> - internal class TracePipelineWriter : PipelineWriter + internal sealed class TracePipelineWriter : PipelineWriter { internal TracePipelineWriter( TraceListenerCommandBase cmdlet, bool writeError, Collection<PSTraceSource> matchingSources) { - ArgumentNullException.ThrowIfNull(cmdlet); + ArgumentNullException.ThrowIfNull(cmdlet); ArgumentNullException.ThrowIfNull(matchingSources); _cmdlet = cmdlet; diff --git a/src/Microsoft.PowerShell.Commands.Utility/resources/CsvCommandStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/CsvCommandStrings.resx index 0eff0d6f84f..dde1be6ab49 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/CsvCommandStrings.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/CsvCommandStrings.resx @@ -130,9 +130,6 @@ <data name="CannotSpecifyQuoteFieldsAndUseQuotes" xml:space="preserve"> <value>You must specify either the -UseQuotes or -QuoteFields parameters, but not both.</value> </data> - <data name="CannotSpecifyIncludeTypeInformationAndNoTypeInformation" xml:space="preserve"> - <value>You must specify either the -IncludeTypeInformation or -NoTypeInformation parameters, but not both.</value> - </data> <data name="CannotSpecifyPathAndLiteralPath" xml:space="preserve"> <value>You must specify either the -Path or -LiteralPath parameters, but not both.</value> </data> @@ -157,4 +154,7 @@ <data name="EOFIsReached" xml:space="preserve"> <value>EOF is reached.</value> </data> + <data name="CannotSpecifyAppendAndNoHeader" xml:space="preserve"> + <value>You must specify either the -Append or -NoHeader parameters, but not both.</value> + </data> </root> diff --git a/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/HResult.cs b/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/HResult.cs index e23f0810ea1..4789bcef06f 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/HResult.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/HResult.cs @@ -4,18 +4,18 @@ namespace Microsoft.PowerShell { /// <summary> - /// HRESULT Wrapper - /// </summary> + /// HRESULT Wrapper + /// </summary> internal enum HResult { - /// <summary> - /// S_OK - /// </summary> + /// <summary> + /// S_OK + /// </summary> Ok = 0x0000, /// <summary> /// S_FALSE. - /// </summary> + /// </summary> False = 0x0001, /// <summary> diff --git a/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/PropVariant.cs b/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/PropVariant.cs index 10db7912b4a..408c59c482a 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/PropVariant.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/WindowsTaskbarJumpList/PropVariant.cs @@ -34,9 +34,7 @@ internal PropVariant(string value) throw new ArgumentException("PropVariantNullString", nameof(value)); } -#pragma warning disable CS0618 // Type or member is obsolete (might get deprecated in future versions _valueType = (ushort)VarEnum.VT_LPWSTR; -#pragma warning restore CS0618 // Type or member is obsolete (might get deprecated in future versions _ptr = Marshal.StringToCoTaskMemUni(value); } diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs index 50d2bd77d0f..0fb85e740f4 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs @@ -196,6 +196,7 @@ internal static int MaxNameLength() "workingdirectory" }; +#pragma warning disable SA1025 // CodeMustNotContainMultipleWhitespaceInARow /// <summary> /// These represent the parameters that are used when starting pwsh. /// We can query in our telemetry to determine how pwsh was invoked. @@ -203,35 +204,36 @@ internal static int MaxNameLength() [Flags] internal enum ParameterBitmap : long { - Command = 0x00000001, // -Command | -c - ConfigurationName = 0x00000002, // -ConfigurationName | -config - CustomPipeName = 0x00000004, // -CustomPipeName - EncodedCommand = 0x00000008, // -EncodedCommand | -e | -ec - EncodedArgument = 0x00000010, // -EncodedArgument - ExecutionPolicy = 0x00000020, // -ExecutionPolicy | -ex | -ep - File = 0x00000040, // -File | -f - Help = 0x00000080, // -Help, -?, /? - InputFormat = 0x00000100, // -InputFormat | -inp | -if - Interactive = 0x00000200, // -Interactive | -i - Login = 0x00000400, // -Login | -l - MTA = 0x00000800, // -MTA - NoExit = 0x00001000, // -NoExit | -noe - NoLogo = 0x00002000, // -NoLogo | -nol - NonInteractive = 0x00004000, // -NonInteractive | -noni - NoProfile = 0x00008000, // -NoProfile | -nop - OutputFormat = 0x00010000, // -OutputFormat | -o | -of - SettingsFile = 0x00020000, // -SettingsFile | -settings - SSHServerMode = 0x00040000, // -SSHServerMode | -sshs - SocketServerMode = 0x00080000, // -SocketServerMode | -sockets - ServerMode = 0x00100000, // -ServerMode | -server - NamedPipeServerMode = 0x00200000, // -NamedPipeServerMode | -namedpipes - STA = 0x00400000, // -STA - Version = 0x00800000, // -Version | -v - WindowStyle = 0x01000000, // -WindowStyle | -w - WorkingDirectory = 0x02000000, // -WorkingDirectory | -wd - ConfigurationFile = 0x04000000, // -ConfigurationFile - NoProfileLoadTime = 0x08000000, // -NoProfileLoadTime - CommandWithArgs = 0x10000000, // -CommandWithArgs | -cwa + Command = 0x0000000000000001, // -Command | -c + ConfigurationName = 0x0000000000000002, // -ConfigurationName | -config + CustomPipeName = 0x0000000000000004, // -CustomPipeName + EncodedCommand = 0x0000000000000008, // -EncodedCommand | -e | -ec + EncodedArgument = 0x0000000000000010, // -EncodedArgument + ExecutionPolicy = 0x0000000000000020, // -ExecutionPolicy | -ex | -ep + File = 0x0000000000000040, // -File | -f + Help = 0x0000000000000080, // -Help, -?, /? + InputFormat = 0x0000000000000100, // -InputFormat | -inp | -if + Interactive = 0x0000000000000200, // -Interactive | -i + Login = 0x0000000000000400, // -Login | -l + MTA = 0x0000000000000800, // -MTA + NoExit = 0x0000000000001000, // -NoExit | -noe + NoLogo = 0x0000000000002000, // -NoLogo | -nol + NonInteractive = 0x0000000000004000, // -NonInteractive | -noni + NoProfile = 0x0000000000008000, // -NoProfile | -nop + OutputFormat = 0x0000000000010000, // -OutputFormat | -o | -of + SettingsFile = 0x0000000000020000, // -SettingsFile | -settings + SSHServerMode = 0x0000000000040000, // -SSHServerMode | -sshs + SocketServerMode = 0x0000000000080000, // -SocketServerMode | -sockets + ServerMode = 0x0000000000100000, // -ServerMode | -server + NamedPipeServerMode = 0x0000000000200000, // -NamedPipeServerMode | -namedpipes + STA = 0x0000000000400000, // -STA + Version = 0x0000000000800000, // -Version | -v + WindowStyle = 0x0000000001000000, // -WindowStyle | -w + WorkingDirectory = 0x0000000002000000, // -WorkingDirectory | -wd + ConfigurationFile = 0x0000000004000000, // -ConfigurationFile + NoProfileLoadTime = 0x0000000008000000, // -NoProfileLoadTime + CommandWithArgs = 0x0000000010000000, // -CommandWithArgs | -cwa + // Enum values for specified ExecutionPolicy EPUnrestricted = 0x0000000100000000, // ExecutionPolicy unrestricted EPRemoteSigned = 0x0000000200000000, // ExecutionPolicy remote signed @@ -241,7 +243,11 @@ internal enum ParameterBitmap : long EPBypass = 0x0000002000000000, // ExecutionPolicy bypass EPUndefined = 0x0000004000000000, // ExecutionPolicy undefined EPIncorrect = 0x0000008000000000, // ExecutionPolicy incorrect + + // V2 Socket Server Mode + V2SocketServerMode = 0x0000100000000000, // -V2SocketServerMode | -v2so } +#pragma warning restore SA1025 // CodeMustNotContainMultipleWhitespaceInARow internal ParameterBitmap ParametersUsed = 0; @@ -597,6 +603,33 @@ internal bool RemoveWorkingDirectoryTrailingCharacter return _removeWorkingDirectoryTrailingCharacter; } } + + internal DateTimeOffset? UTCTimestamp + { + get + { + AssertArgumentsParsed(); + return _utcTimestamp; + } + } + + internal string? Token + { + get + { + AssertArgumentsParsed(); + return _token; + } + } + + internal bool V2SocketServerMode + { + get + { + AssertArgumentsParsed(); + return _v2SocketServerMode; + } + } #endif #endregion Internal properties @@ -916,6 +949,14 @@ private void ParseHelper(string[] args) _showBanner = false; ParametersUsed |= ParameterBitmap.SocketServerMode; } +#if !UNIX + else if (MatchSwitch(switchKey, "v2socketservermode", "v2so")) + { + _v2SocketServerMode = true; + _showBanner = false; + ParametersUsed |= ParameterBitmap.V2SocketServerMode; + } +#endif else if (MatchSwitch(switchKey, "servermode", "s")) { _serverMode = true; @@ -1176,6 +1217,37 @@ private void ParseHelper(string[] args) { _removeWorkingDirectoryTrailingCharacter = true; } + else if (MatchSwitch(switchKey, "token", "to")) + { + ++i; + if (i >= args.Length) + { + SetCommandLineError( + string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.MissingMandatoryArgument, "-Token")); + break; + } + + _token = args[i]; + + // Not adding anything to ParametersUsed, because it is required with V2 socket server mode + // So, we can assume it based on that bit + } + else if (MatchSwitch(switchKey, "utctimestamp", "utc")) + { + ++i; + if (i >= args.Length) + { + SetCommandLineError( + string.Format(CultureInfo.CurrentCulture, CommandLineParameterParserStrings.MissingMandatoryArgument, "-UTCTimestamp")); + break; + } + + // Parse as iso8601UtcString + _utcTimestamp = DateTimeOffset.ParseExact(args[i], "yyyy-MM-dd'T'HH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind); + + // Not adding anything to ParametersUsed, because it is required with V2 socket server mode + // So, we can assume it based on that bit + } #endif else { @@ -1530,6 +1602,9 @@ private bool CollectArgs(string[] args, ref int i) } private bool _socketServerMode; +#if !UNIX + private bool _v2SocketServerMode; +#endif private bool _serverMode; private bool _namedPipeServerMode; private bool _sshServerMode; @@ -1562,6 +1637,10 @@ private bool CollectArgs(string[] args, ref int i) private string? _executionPolicy; private string? _settingsFile; private string? _workingDirectory; +#if !UNIX + private string? _token; + private DateTimeOffset? _utcTimestamp; +#endif #if !UNIX private ProcessWindowStyle? _windowStyle; diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleControl.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleControl.cs index 8857c64fa50..7bda4bc5688 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleControl.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleControl.cs @@ -211,41 +211,6 @@ internal struct FONTSIGNATURE internal DWORD fsCsb1; } - [StructLayout(LayoutKind.Sequential)] - internal struct CHARSETINFO - { - // From public\sdk\inc\wingdi.h - internal uint ciCharset; // Character set value. - internal uint ciACP; // ANSI code-page identifier. - internal FONTSIGNATURE fs; - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - internal struct TEXTMETRIC - { - // From public\sdk\inc\wingdi.h - public int tmHeight; - public int tmAscent; - public int tmDescent; - public int tmInternalLeading; - public int tmExternalLeading; - public int tmAveCharWidth; - public int tmMaxCharWidth; - public int tmWeight; - public int tmOverhang; - public int tmDigitizedAspectX; - public int tmDigitizedAspectY; - public char tmFirstChar; - public char tmLastChar; - public char tmDefaultChar; - public char tmBreakChar; - public byte tmItalic; - public byte tmUnderlined; - public byte tmStruckOut; - public byte tmPitchAndFamily; - public byte tmCharSet; - } - #region SentInput Data Structures [StructLayout(LayoutKind.Sequential)] diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs index ab9bdab568d..daf69d50251 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHost.cs @@ -54,16 +54,11 @@ internal sealed partial class ConsoleHost internal const int ExitCodeCtrlBreak = 128 + 21; // SIGBREAK internal const int ExitCodeInitFailure = 70; // Internal Software Error internal const int ExitCodeBadCommandLineParameter = 64; // Command Line Usage Error - private const uint SPI_GETSCREENREADER = 0x0046; #if UNIX internal const string DECCKM_ON = "\x1b[?1h"; internal const string DECCKM_OFF = "\x1b[?1l"; #endif - [DllImport("user32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool SystemParametersInfo(uint uiAction, uint uiParam, ref bool pvParam, uint fWinIni); - /// <summary> /// Internal Entry point in msh console host implementation. /// </summary> @@ -153,13 +148,16 @@ internal static int Start( try { string profileDir = Platform.CacheDirectory; -#if !UNIX - if (!Directory.Exists(profileDir)) + if (!string.IsNullOrEmpty(profileDir)) { - Directory.CreateDirectory(profileDir); - } +#if !UNIX + if (!Directory.Exists(profileDir)) + { + Directory.CreateDirectory(profileDir); + } #endif - ProfileOptimization.SetProfileRoot(profileDir); + ProfileOptimization.SetProfileRoot(profileDir); + } } catch { @@ -198,7 +196,26 @@ internal static int Start( } // Servermode parameter validation check. - if ((s_cpp.ServerMode && s_cpp.NamedPipeServerMode) || (s_cpp.ServerMode && s_cpp.SocketServerMode) || (s_cpp.NamedPipeServerMode && s_cpp.SocketServerMode)) + int serverModeCount = 0; + if (s_cpp.ServerMode) + { + serverModeCount++; + } + if (s_cpp.NamedPipeServerMode) + { + serverModeCount++; + } + if (s_cpp.SocketServerMode) + { + serverModeCount++; + } +#if !UNIX + if (s_cpp.V2SocketServerMode) + { + serverModeCount++; + } +#endif + if (serverModeCount > 1) { s_tracer.TraceError("Conflicting server mode parameters, parameters must be used exclusively."); s_theConsoleHost?.ui.WriteErrorLine(ConsoleHostStrings.ConflictingServerModeParameters); @@ -242,6 +259,34 @@ internal static int Start( configurationName: s_cpp.ConfigurationName); exitCode = 0; } +#if !UNIX + else if (s_cpp.V2SocketServerMode) + { + if (s_cpp.Token == null) + { + s_tracer.TraceError("Token is required for V2SocketServerMode."); + s_theConsoleHost?.ui.WriteErrorLine(string.Format(CultureInfo.CurrentCulture, ConsoleHostStrings.MissingMandatoryParameter, "-Token", "-V2SocketServerMode")); + return ExitCodeBadCommandLineParameter; + } + + if (s_cpp.UTCTimestamp == null) + { + s_tracer.TraceError("UTCTimestamp is required for V2SocketServerMode."); + s_theConsoleHost?.ui.WriteErrorLine(string.Format(CultureInfo.CurrentCulture, ConsoleHostStrings.MissingMandatoryParameter, "-UTCTimestamp", "-v2socketservermode")); + return ExitCodeBadCommandLineParameter; + } + + ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("V2SocketServerMode", s_cpp.ParametersUsedAsDouble); + ProfileOptimization.StartProfile("StartupProfileData-V2SocketServerMode"); + HyperVSocketMediator.Run( + initialCommand: s_cpp.InitialCommand, + configurationName: s_cpp.ConfigurationName, + token: s_cpp.Token, + tokenCreationTime: s_cpp.UTCTimestamp.Value); + + exitCode = 0; + } +#endif else if (s_cpp.SocketServerMode) { ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("SocketServerMode", s_cpp.ParametersUsedAsDouble); @@ -276,7 +321,7 @@ internal static int Start( } s_theConsoleHost.BindBreakHandler(); - PSHost.IsStdOutputRedirected = Console.IsOutputRedirected; + IsStdOutputRedirected = Console.IsOutputRedirected; // Send startup telemetry for ConsoleHost startup ApplicationInsightsTelemetry.SendPSCoreStartupTelemetry("Normal", s_cpp.ParametersUsedAsDouble); @@ -1633,35 +1678,6 @@ private void CreateRunspace(RunspaceCreationEventArgs runspaceCreationArgs) } } - /// <summary> - /// Check if a screen reviewer utility is running. - /// When a screen reader is running, we don't auto-load the PSReadLine module at startup, - /// since PSReadLine is not accessibility-friendly enough as of today. - /// </summary> - private bool IsScreenReaderActive() - { - if (_screenReaderActive.HasValue) - { - return _screenReaderActive.Value; - } - - _screenReaderActive = false; - if (Platform.IsWindowsDesktop) - { - // Note: this API can detect if a third-party screen reader is active, such as NVDA, but not the in-box Windows Narrator. - // Quoted from https://learn.microsoft.com/windows/win32/api/winuser/nf-winuser-systemparametersinfoa about the - // accessibility parameter 'SPI_GETSCREENREADER': - // "Narrator, the screen reader that is included with Windows, does not set the SPI_SETSCREENREADER or SPI_GETSCREENREADER flags." - bool enabled = false; - if (SystemParametersInfo(SPI_GETSCREENREADER, 0, ref enabled, 0)) - { - _screenReaderActive = enabled; - } - } - - return _screenReaderActive.Value; - } - private static bool LoadPSReadline() { // Don't load PSReadline if: @@ -1712,7 +1728,6 @@ private void DoCreateRunspace(RunspaceCreationEventArgs args) bool psReadlineFailed = false; // Load PSReadline by default unless there is no use: - // - screen reader is active, such as NVDA, indicating non-visual access // - we're running a command/file and just exiting // - stdin is redirected by a parent process // - we're not interactive @@ -1723,26 +1738,18 @@ private void DoCreateRunspace(RunspaceCreationEventArgs args) ReadOnlyCollection<ModuleSpecification> defaultImportModulesList = null; if (!customConfigurationProvided && LoadPSReadline()) { - if (IsScreenReaderActive()) + // Create and open Runspace with PSReadline. + defaultImportModulesList = DefaultInitialSessionState.Modules; + DefaultInitialSessionState.ImportPSModule(new[] { "PSReadLine" }); + consoleRunspace = RunspaceFactory.CreateRunspace(this, DefaultInitialSessionState); + try { - s_theConsoleHost.UI.WriteLine(ManagedEntranceStrings.PSReadLineDisabledWhenScreenReaderIsActive); - s_theConsoleHost.UI.WriteLine(); + OpenConsoleRunspace(consoleRunspace, args.StaMode); } - else + catch (Exception) { - // Create and open Runspace with PSReadline. - defaultImportModulesList = DefaultInitialSessionState.Modules; - DefaultInitialSessionState.ImportPSModule(new[] { "PSReadLine" }); - consoleRunspace = RunspaceFactory.CreateRunspace(this, DefaultInitialSessionState); - try - { - OpenConsoleRunspace(consoleRunspace, args.StaMode); - } - catch (Exception) - { - consoleRunspace = null; - psReadlineFailed = true; - } + consoleRunspace = null; + psReadlineFailed = true; } } @@ -1879,6 +1886,7 @@ private void DoRunspaceInitialization(RunspaceCreationEventArgs args) { s_theConsoleHost.UI.WriteLine(ManagedEntranceStrings.ShellBannerCLAuditMode); } + break; case PSLanguageMode.NoLanguage: @@ -2193,7 +2201,6 @@ private void ReportException(Exception e, Executor exec) if (e1 != null) { // that didn't work. Write out the error ourselves as a last resort. - ReportExceptionFallback(e, null); } } @@ -2214,7 +2221,7 @@ private void ReportExceptionFallback(Exception e, string header) Console.Error.WriteLine(header); } - if (e == null) + if (e is null) { return; } @@ -2222,7 +2229,9 @@ private void ReportExceptionFallback(Exception e, string header) // See if the exception has an error record attached to it... ErrorRecord er = null; if (e is IContainsErrorRecord icer) + { er = icer.ErrorRecord; + } if (e is PSRemotingTransportException) { @@ -2239,8 +2248,22 @@ private void ReportExceptionFallback(Exception e, string header) } // Add the position message for the error if it's available. - if (er != null && er.InvocationInfo != null) + if (er?.InvocationInfo is { }) + { Console.Error.WriteLine(er.InvocationInfo.PositionMessage); + } + + // Print the stack trace. + Console.Error.WriteLine($"\n--- {e.GetType().FullName} ---"); + Console.Error.WriteLine(e.StackTrace); + + Exception inner = e.InnerException; + while (inner is { }) + { + Console.Error.WriteLine($"--- inner {inner.GetType().FullName} ---"); + Console.Error.WriteLine(inner.StackTrace); + inner = inner.InnerException; + } } /// <summary> @@ -2560,14 +2583,7 @@ internal void Run(bool inputLoopIsNested) // Evaluate any suggestions if (previousResponseWasEmpty == false) { - if (ExperimentalFeature.IsEnabled(ExperimentalFeature.PSFeedbackProvider)) - { - EvaluateFeedbacks(ui); - } - else - { - EvaluateSuggestions(ui); - } + EvaluateFeedbacks(ui); } // Then output the prompt @@ -2745,6 +2761,7 @@ e is RemoteException || #endif } } + // NTRAID#Windows Out Of Band Releases-915506-2005/09/09 // Removed HandleUnexpectedExceptions infrastructure finally @@ -2890,44 +2907,6 @@ private void EvaluateFeedbacks(ConsoleHostUserInterface ui) } } - private void EvaluateSuggestions(ConsoleHostUserInterface ui) - { - // Output any training suggestions - try - { - List<string> suggestions = HostUtilities.GetSuggestion(_parent.Runspace); - - if (suggestions.Count > 0) - { - ui.WriteLine(); - } - - bool first = true; - foreach (string suggestion in suggestions) - { - if (!first) - ui.WriteLine(); - - ui.WriteLine(suggestion); - - first = false; - } - } - catch (TerminateException) - { - // A variable breakpoint may be hit by HostUtilities.GetSuggestion. The debugger throws TerminateExceptions to stop the execution - // of the current statement; we do not want to treat these exceptions as errors. - } - catch (Exception e) - { - // Catch-all OK. This is a third-party call-out. - ui.WriteErrorLine(e.Message); - - LocalRunspace localRunspace = (LocalRunspace)_parent.Runspace; - localRunspace.GetExecutionContext.AppendDollarError(e); - } - } - private string EvaluatePrompt() { string promptString = _promptExec.ExecuteCommandAndGetResultAsString("prompt", out _); @@ -3053,7 +3032,6 @@ private sealed class ConsoleHostStartupException : Exception private bool _setShouldExitCalled; private bool _isRunningPromptLoop; private bool _wasInitialCommandEncoded; - private bool? _screenReaderActive; // hostGlobalLock is used to sync public method calls (in case multiple threads call into the host) and access to // state that persists across method calls, like progress data. It's internal because the ui object also diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs index 9587d6cd553..f0da0d99547 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleHostUserInterface.cs @@ -578,7 +578,7 @@ private static bool shouldUnsetMode( [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void WriteToConsole(char c, bool transcribeResult) { - ReadOnlySpan<char> value = stackalloc char[1] { c }; + ReadOnlySpan<char> value = [c]; WriteToConsole(value, transcribeResult); } @@ -1396,6 +1396,7 @@ public override void WriteErrorLine(string value) } else { + value = GetOutputString(value, SupportsVirtualTerminal); Console.Error.WriteLine(value); } } diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleTextWriter.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleTextWriter.cs index 97a02cb7123..a82156ce1c8 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleTextWriter.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleTextWriter.cs @@ -76,8 +76,8 @@ public override void Write(char c) { - ReadOnlySpan<char> c1 = stackalloc char[1] { c }; - _ui.WriteToConsole(c1, transcribeResult: true); + ReadOnlySpan<char> value = [c]; + _ui.WriteToConsole(value, transcribeResult: true); } public override diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/Executor.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/Executor.cs index 38924f3e397..354c61fb8f3 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/Executor.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/Executor.cs @@ -15,7 +15,7 @@ namespace Microsoft.PowerShell /// <summary> /// Executor wraps a Pipeline instance, and provides helper methods for executing commands in that pipeline. It is used to /// provide bookkeeping and structure to the use of pipeline in such a way that they can be interrupted and cancelled by a - /// break event handler, and to track nesting of pipelines (which happens with interrupted input loops (aka subshells) and + /// break event handler, and to track nesting of pipelines (which happens with interrupted input loops (aka subshells) and /// use of tab-completion in prompts). The bookkeeping is necessary because the break handler is static and global, and /// there is no means for tying a break handler to an instance of an object. /// diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ManagedEntrance.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ManagedEntrance.cs index 6dfd5d54e6f..acfdea07153 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/ManagedEntrance.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/ManagedEntrance.cs @@ -86,9 +86,9 @@ public static int Start([MarshalAs(UnmanagedType.LPArray, ArraySubType = Unmanag int exitCode = 0; try { - var banner = string.Format( + string banner = string.Format( CultureInfo.InvariantCulture, - ManagedEntranceStrings.ShellBannerNonWindowsPowerShell, + ManagedEntranceStrings.ShellBannerPowerShell, PSVersionInfo.GitCommitId); ConsoleHost.DefaultInitialSessionState = InitialSessionState.CreateDefault2(); diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/StopTranscriptCmdlet.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/StopTranscriptCmdlet.cs index 71e90854dc4..093f7c147dc 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/StopTranscriptCmdlet.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/StopTranscriptCmdlet.cs @@ -25,7 +25,7 @@ protected override { return; } - + try { string outFilename = Host.UI.StopTranscribing(); diff --git a/src/Microsoft.PowerShell.ConsoleHost/host/msh/UpdatesNotification.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/UpdatesNotification.cs index 28cd31473dd..eb4557c04d2 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/UpdatesNotification.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/UpdatesNotification.cs @@ -28,6 +28,9 @@ internal static class UpdatesNotification private const string StableBuildInfoURL = "https://aka.ms/pwsh-buildinfo-stable"; private const string PreviewBuildInfoURL = "https://aka.ms/pwsh-buildinfo-preview"; + private const int NotificationDelayDays = 7; + private const int UpdateCheckBackoffDays = 7; + /// <summary> /// The version of new update is persisted using a file, not as the file content, but instead baked in the file name in the following template: /// `update{notification-type}_{version}_{publish-date}` -- held by 's_updateFileNameTemplate', @@ -57,12 +60,12 @@ internal static class UpdatesNotification static UpdatesNotification() { s_notificationType = GetNotificationType(); - CanNotifyUpdates = s_notificationType != NotificationType.Off; + CanNotifyUpdates = s_notificationType != NotificationType.Off + && Platform.TryDeriveFromCache(PSVersionInfo.GitCommitId, out s_cacheDirectory); if (CanNotifyUpdates) { s_enumOptions = new EnumerationOptions(); - s_cacheDirectory = Path.Combine(Platform.CacheDirectory, PSVersionInfo.GitCommitId); // Build the template/pattern strings for the configured notification type. string typeNum = ((int)s_notificationType).ToString(); @@ -89,9 +92,18 @@ internal static void ShowUpdateNotification(PSHostUserInterface hostUI) if (TryParseUpdateFile( updateFilePath: out _, out SemanticVersion lastUpdateVersion, - lastUpdateDate: out _) + out DateTime lastUpdateDate) && lastUpdateVersion != null) { + DateTime today = DateTime.UtcNow; + if ((today - lastUpdateDate).TotalDays < NotificationDelayDays) + { + // The update was out less than 1 week ago and it's possible the packages are still rolling out. + // We only show the notification when the update is at least 1 week old, to reduce the chance that + // users see the notification but cannot get the new update when they try to install it. + return; + } + string releaseTag = lastUpdateVersion.ToString(); string notificationMsgTemplate = s_notificationType == NotificationType.LTS ? ManagedEntranceStrings.LTSUpdateNotificationMessage @@ -169,7 +181,7 @@ internal static async Task CheckForUpdates() out DateTime lastUpdateDate); DateTime today = DateTime.UtcNow; - if (parseSuccess && updateFilePath != null && (today - lastUpdateDate).TotalDays < 7) + if (parseSuccess && updateFilePath != null && (today - lastUpdateDate).TotalDays < UpdateCheckBackoffDays) { // There is an existing update file, and the last update was less than 1 week ago. // It's unlikely a new version is released within 1 week, so we can skip this check. diff --git a/src/Microsoft.PowerShell.ConsoleHost/resources/CommandLineParameterParserStrings.resx b/src/Microsoft.PowerShell.ConsoleHost/resources/CommandLineParameterParserStrings.resx index 34bb696c33c..33445ceebd2 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/resources/CommandLineParameterParserStrings.resx +++ b/src/Microsoft.PowerShell.ConsoleHost/resources/CommandLineParameterParserStrings.resx @@ -225,4 +225,7 @@ Valid formats are: <data name="InvalidExecutionPolicyArgument" xml:space="preserve"> <value>Invalid ExecutionPolicy value '{0}'.</value> </data> + <data name="MissingMandatoryArgument" xml:space="preserve"> + <value>An argument is required to be supplied to the '{0}' parameter.</value> + </data> </root> diff --git a/src/Microsoft.PowerShell.ConsoleHost/resources/ConsoleHostStrings.resx b/src/Microsoft.PowerShell.ConsoleHost/resources/ConsoleHostStrings.resx index 80b3d4aafe0..9bc06e0d42f 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/resources/ConsoleHostStrings.resx +++ b/src/Microsoft.PowerShell.ConsoleHost/resources/ConsoleHostStrings.resx @@ -185,4 +185,7 @@ The current session does not support debugging; execution will continue. <data name="PushRunspaceNotRemote" xml:space="preserve"> <value>PushRunspace can only push a remote runspace.</value> </data> + <data name="MissingMandatoryParameter" xml:space="preserve"> + <value>The '{0}' parameter is mandatory and must be specified when using the '{1}' parameter.</value> + </data> </root> diff --git a/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx b/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx index a57d044a8be..44ebf0fb538 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx +++ b/src/Microsoft.PowerShell.ConsoleHost/resources/ManagedEntranceStrings.resx @@ -117,7 +117,7 @@ <resheader name="writer"> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> </resheader> - <data name="ShellBannerNonWindowsPowerShell" xml:space="preserve"> + <data name="ShellBannerPowerShell" xml:space="preserve"> <value>PowerShell {0}</value> </data> <data name="ShellBannerCLMode" xml:space="preserve"> @@ -132,9 +132,6 @@ <data name="ShellBannerRLMode" xml:space="preserve"> <value>[Restricted Language Mode]</value> </data> - <data name="PSReadLineDisabledWhenScreenReaderIsActive" xml:space="preserve"> - <value>Warning: PowerShell detected that you might be using a screen reader and has disabled PSReadLine for compatibility purposes. If you want to re-enable it, run 'Import-Module PSReadLine'.</value> - </data> <data name="PreviewUpdateNotificationMessage" xml:space="preserve"> <value> {1} A new PowerShell preview release is available: v{0} {2} {1} Upgrade now, or check out the release page at:{3}{2} @@ -162,9 +159,9 @@ [-CustomPipeName <string>] [-EncodedCommand <Base64EncodedCommand>] [-ExecutionPolicy <ExecutionPolicy>] [-InputFormat {Text | XML}] [-Interactive] [-MTA] [-NoExit] [-NoLogo] [-NonInteractive] [-NoProfile] - [-NoProfileLoadTime] [-OutputFormat {Text | XML}] - [-SettingsFile <filePath>] [-SSHServerMode] [-STA] - [-Version] [-WindowStyle <style>] + [-NoProfileLoadTime] [-OutputFormat {Text | XML}] + [-SettingsFile <filePath>] [-SSHServerMode] [-STA] + [-Version] [-WindowStyle <style>] [-WorkingDirectory <directoryPath>] pwsh[.exe] -h | -Help | -? | /? diff --git a/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj b/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj index 896e517b074..dadff652e53 100644 --- a/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj +++ b/src/Microsoft.PowerShell.CoreCLR.Eventing/Microsoft.PowerShell.CoreCLR.Eventing.csproj @@ -8,7 +8,7 @@ <ItemGroup> <!-- the following package(s) are from https://github.com/dotnet/corefx --> - <PackageReference Include="System.Diagnostics.EventLog" Version="10.0.0-preview.6.25358.103" /> + <PackageReference Include="System.Diagnostics.EventLog" Version="11.0.0-preview.3.26207.106" /> </ItemGroup> </Project> diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/AddLocalGroupMemberCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/AddLocalGroupMemberCommand.cs index 375a8101075..59cb3067efc 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/AddLocalGroupMemberCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/AddLocalGroupMemberCommand.cs @@ -301,4 +301,3 @@ private void ProcessSid(SecurityIdentifier groupSid) } } - diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/DisableLocalUserCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/DisableLocalUserCommand.cs index 09e7bdf9452..592c77726fe 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/DisableLocalUserCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/DisableLocalUserCommand.cs @@ -217,4 +217,3 @@ private bool CheckShouldProcess(string target) } } - diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/EnableLocalUserCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/EnableLocalUserCommand.cs index e321a03e266..88627ba2e33 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/EnableLocalUserCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/EnableLocalUserCommand.cs @@ -217,4 +217,3 @@ private bool CheckShouldProcess(string target) } } - diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalGroupCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalGroupCommand.cs index 3965c362335..4c11a3c4c36 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalGroupCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalGroupCommand.cs @@ -168,4 +168,3 @@ private void ProcessSids() } } - diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalGroupMemberCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalGroupMemberCommand.cs index 8be09e1ded5..0b3625f6ef7 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalGroupMemberCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalGroupMemberCommand.cs @@ -233,4 +233,3 @@ private IEnumerable<LocalPrincipal> ProcessSid(SecurityIdentifier groupSid) } } - diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalUserCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalUserCommand.cs index e469d043ffa..9f8d3b9311b 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalUserCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/GetLocalUserCommand.cs @@ -170,4 +170,3 @@ private void ProcessSids() } } - diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/NewLocalGroupCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/NewLocalGroupCommand.cs index 59e4f4ca13f..b709d22a485 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/NewLocalGroupCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/NewLocalGroupCommand.cs @@ -118,4 +118,3 @@ private bool CheckShouldProcess(string target) } } - diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/NewLocalUserCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/NewLocalUserCommand.cs index b3916f46071..d3402b5a4c9 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/NewLocalUserCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/NewLocalUserCommand.cs @@ -292,4 +292,3 @@ private bool CheckShouldProcess(string target) } } - diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalGroupCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalGroupCommand.cs index 0c0af710af1..981643cf04c 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalGroupCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalGroupCommand.cs @@ -210,4 +210,3 @@ private bool CheckShouldProcess(string target) } } - diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalGroupMemberCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalGroupMemberCommand.cs index 7e132405b2a..47d0a77784e 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalGroupMemberCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalGroupMemberCommand.cs @@ -299,4 +299,3 @@ private void ProcessSid(SecurityIdentifier groupSid) } } - diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalUserCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalUserCommand.cs index 0c61da2e117..6d6081fef7e 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalUserCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RemoveLocalUserCommand.cs @@ -211,4 +211,3 @@ private bool CheckShouldProcess(string target) } } - diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RenameLocalGroupCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RenameLocalGroupCommand.cs index f32e3365086..c594fccb889 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RenameLocalGroupCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RenameLocalGroupCommand.cs @@ -230,4 +230,3 @@ private bool CheckShouldProcess(string groupName, string newName) } } - diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RenameLocalUserCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RenameLocalUserCommand.cs index e6b1297a7d5..c23cf41ac61 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RenameLocalUserCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/RenameLocalUserCommand.cs @@ -230,4 +230,3 @@ private bool CheckShouldProcess(string userName, string newName) } } - diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/SetLocalGroupCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/SetLocalGroupCommand.cs index b1943971e3d..49ae31dacc7 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/SetLocalGroupCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/SetLocalGroupCommand.cs @@ -177,4 +177,3 @@ private bool CheckShouldProcess(string target) } } - diff --git a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/SetLocalUserCommand.cs b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/SetLocalUserCommand.cs index 8fafa52c9e4..3bfdc24ac03 100644 --- a/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/SetLocalUserCommand.cs +++ b/src/Microsoft.PowerShell.LocalAccounts/LocalAccounts/Commands/SetLocalUserCommand.cs @@ -320,4 +320,3 @@ private bool CheckShouldProcess(string target) } } - diff --git a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj index e20cb27c38d..50d431225c7 100644 --- a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj +++ b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj @@ -16,28 +16,40 @@ <ItemGroup> <!-- This section is to force the version of non-direct dependencies --> - <PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.0-preview.6.25358.103" /> - <PackageReference Include="Microsoft.Extensions.ObjectPool" Version="10.0.0-preview.6.25358.103" /> + <PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="11.0.0-preview.3.26207.106" /> + <PackageReference Include="Microsoft.Extensions.ObjectPool" Version="11.0.0-preview.3.26207.106" /> <!-- the following package(s) are from https://github.com/dotnet/fxdac --> - <PackageReference Include="System.Data.SqlClient" Version="4.9.0" /> + <PackageReference Include="System.Data.SqlClient" Version="4.9.1" /> <!-- the following package(s) are from https://github.com/dotnet/corefx --> - <PackageReference Include="System.IO.Packaging" Version="10.0.0-preview.6.25358.103" /> - <PackageReference Include="System.Net.Http.WinHttpHandler" Version="10.0.0-preview.6.25358.103" /> - <!-- Removing due to NU1510 --> - <!-- PackageReference Include="System.Text.Encodings.Web" Version="9.0.2" /--> - <!-- - the following package(s) are from https://github.com/dotnet/wcf - they are pinned to the version 4.10.x due to a breaking change in newer versions. - see https://github.com/PowerShell/PowerShell/issues/19238 for details. - --> - <PackageReference Include="System.ServiceModel.Duplex" Version="4.10.3" NoWarn="NU1605" /> - <PackageReference Include="System.ServiceModel.Http" Version="4.10.3" NoWarn="NU1605" /> - <PackageReference Include="System.ServiceModel.NetTcp" Version="4.10.3" NoWarn="NU1605" /> - <PackageReference Include="System.ServiceModel.Primitives" Version="4.10.3" NoWarn="NU1605" /> - <PackageReference Include="System.ServiceModel.Security" Version="4.10.3" NoWarn="NU1605" /> - <PackageReference Include="System.Private.ServiceModel" Version="4.10.3" NoWarn="NU1605" /> + <PackageReference Include="System.IO.Packaging" Version="11.0.0-preview.3.26207.106" /> + <PackageReference Include="System.Net.Http.WinHttpHandler" Version="11.0.0-preview.3.26207.106" /> + <!-- the following package(s) are from https://github.com/dotnet/wcf --> + <PackageReference Include="System.ServiceModel.Http" Version="10.0.652802" /> + <PackageReference Include="System.ServiceModel.NetTcp" Version="10.0.652802" /> + <PackageReference Include="System.ServiceModel.Primitives" Version="10.0.652802" /> <!-- the source could not be found for the following package(s) --> - <PackageReference Include="Microsoft.Windows.Compatibility" Version="10.0.0-preview.6.25358.103" /> + <PackageReference Include="Microsoft.Windows.Compatibility" Version="11.0.0-preview.3.26207.106" /> </ItemGroup> -</Project> + <!-- + This target is invoked explicitly by Start-TypeGen in build.psm1 to collect the list of + reference assembly paths needed by TypeCatalogGen. It is not run during a normal build. + + To find the available properties of '_ReferencesFromRAR' when switching to a new dotnet sdk: + 1. Create a dummy project using the new dotnet sdk. + 2. Build the dummy project with: + dotnet msbuild ./dummy.csproj /t:ResolveAssemblyReferencesDesignTime /fileLogger /noconsolelogger /v:diag + 3. Search '_ReferencesFromRAR' in the produced 'msbuild.log' file. + --> + <Target Name="_GetDependencies" DependsOnTargets="ResolveAssemblyReferencesDesignTime"> + <ItemGroup> + <!-- + Excludes 'Microsoft.Management.Infrastructure' from the type catalog reference list, + as it is provided separately at runtime and must not be included in the generated catalog. + --> + <_RefAssemblyPath Include="%(_ReferencesFromRAR.OriginalItemSpec)%3B" Condition=" '%(_ReferencesFromRAR.NuGetPackageId)' != 'Microsoft.Management.Infrastructure' " /> + </ItemGroup> + <WriteLinesToFile File="$(_DependencyFile)" Lines="@(_RefAssemblyPath)" Overwrite="true" /> + </Target> + +</Project> \ No newline at end of file diff --git a/src/Microsoft.PowerShell.Security/security/AclCommands.cs b/src/Microsoft.PowerShell.Security/security/AclCommands.cs index 1b5beffa49e..e39b296d154 100644 --- a/src/Microsoft.PowerShell.Security/security/AclCommands.cs +++ b/src/Microsoft.PowerShell.Security/security/AclCommands.cs @@ -207,7 +207,7 @@ public static string GetOwner(PSObject instance) throw PSTraceSource.NewArgumentNullException(nameof(instance)); } - if (!(instance.BaseObject is ObjectSecurity sd)) + if (instance.BaseObject is not ObjectSecurity sd) { throw PSTraceSource.NewArgumentNullException(nameof(instance)); } @@ -246,7 +246,7 @@ public static string GetGroup(PSObject instance) throw PSTraceSource.NewArgumentNullException(nameof(instance)); } - if (!(instance.BaseObject is ObjectSecurity sd)) + if (instance.BaseObject is not ObjectSecurity sd) { throw PSTraceSource.NewArgumentNullException(nameof(instance)); } @@ -570,7 +570,7 @@ public static string GetSddl(PSObject instance) throw PSTraceSource.NewArgumentNullException(nameof(instance)); } - if (!(instance.BaseObject is ObjectSecurity sd)) + if (instance.BaseObject is not ObjectSecurity sd) { throw PSTraceSource.NewArgumentNullException(nameof(instance)); } diff --git a/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs b/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs index 2a05a9bf30d..37c687a7770 100644 --- a/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs +++ b/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs @@ -3424,7 +3424,6 @@ internal static IntPtr GetOwnerWindow(PSHost host) return IntPtr.Zero; } #else - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private static IntPtr hWnd = IntPtr.Zero; private static bool firstRun = true; @@ -3459,8 +3458,7 @@ private static bool IsUIAllowed(PSHost host) return false; uint SessionId; - uint ProcessId = (uint)System.Diagnostics.Process.GetCurrentProcess().Id; - if (!SMASecurity.NativeMethods.ProcessIdToSessionId(ProcessId, out SessionId)) + if (!SMASecurity.NativeMethods.ProcessIdToSessionId((uint)Environment.ProcessId, out SessionId)) return false; if (SessionId == 0) diff --git a/src/Microsoft.PowerShell.Security/security/SecureStringCommands.cs b/src/Microsoft.PowerShell.Security/security/SecureStringCommands.cs index c727476c0a3..29179a7046d 100644 --- a/src/Microsoft.PowerShell.Security/security/SecureStringCommands.cs +++ b/src/Microsoft.PowerShell.Security/security/SecureStringCommands.cs @@ -311,8 +311,7 @@ protected override void ProcessRecord() byte[] iv = null; // If this is a V2 package - if (String.IndexOf(SecureStringHelper.SecureStringExportHeader, - StringComparison.OrdinalIgnoreCase) == 0) + if (String.StartsWith(SecureStringHelper.SecureStringExportHeader, StringComparison.OrdinalIgnoreCase)) { try { diff --git a/src/Microsoft.WSMan.Management/ConfigProvider.cs b/src/Microsoft.WSMan.Management/ConfigProvider.cs index f1fc3a36277..55141379faa 100644 --- a/src/Microsoft.WSMan.Management/ConfigProvider.cs +++ b/src/Microsoft.WSMan.Management/ConfigProvider.cs @@ -62,7 +62,7 @@ public sealed class WSManConfigProvider : NavigationCmdletProvider, ICmdletProvi string ICmdletProviderSupportsHelp.GetHelpMaml(string helpItemName, string path) { // Get the leaf node from the path for which help is requested. - int ChildIndex = path.LastIndexOf("\\", StringComparison.OrdinalIgnoreCase); + int ChildIndex = path.LastIndexOf('\\'); if (ChildIndex == -1) { // Means we are at host level, where no new-item is supported. Return empty string. diff --git a/src/Microsoft.WSMan.Management/CredSSP.cs b/src/Microsoft.WSMan.Management/CredSSP.cs index 03c71f0edb1..a8457ab5bc2 100644 --- a/src/Microsoft.WSMan.Management/CredSSP.cs +++ b/src/Microsoft.WSMan.Management/CredSSP.cs @@ -514,7 +514,7 @@ private void EnableClientSideSettings() try { XmlDocument xmldoc = new XmlDocument(); - + // push the xml string with credssp enabled xmldoc.LoadXml(m_SessionObj.Put(helper.CredSSP_RUri, newxmlcontent, 0)); diff --git a/src/Microsoft.WSMan.Management/CurrentConfigurations.cs b/src/Microsoft.WSMan.Management/CurrentConfigurations.cs index f4c252c4d1d..3182c04c535 100644 --- a/src/Microsoft.WSMan.Management/CurrentConfigurations.cs +++ b/src/Microsoft.WSMan.Management/CurrentConfigurations.cs @@ -11,7 +11,7 @@ namespace Microsoft.WSMan.Management /// Class that queries the server and gets current configurations. /// Also provides a generic way to update the configurations. /// </summary> - internal class CurrentConfigurations + internal sealed class CurrentConfigurations { /// <summary> /// Prefix used to add NameSpace of root element to namespace manager. diff --git a/src/Microsoft.WSMan.Management/Interop.cs b/src/Microsoft.WSMan.Management/Interop.cs index e5dd462e2f7..3abb938a572 100644 --- a/src/Microsoft.WSMan.Management/Interop.cs +++ b/src/Microsoft.WSMan.Management/Interop.cs @@ -734,18 +734,19 @@ public interface IWSManResourceLocator [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings")] string ResourceUri { - // IDL: HRESULT resourceUri ([out, retval] BSTR* ReturnValue); + // IDL: HRESULT resourceUri (BSTR value); + [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1212:PropertyAccessorsMustFollowOrder", Justification = "COM interface defines put_ before get_.")] [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "resource")] [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings")] [DispId(1)] - [return: MarshalAs(UnmanagedType.BStr)] - get; + set; - // IDL: HRESULT resourceUri (BSTR value); + // IDL: HRESULT resourceUri ([out, retval] BSTR* ReturnValue); [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "resource")] [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings")] [DispId(1)] - set; + [return: MarshalAs(UnmanagedType.BStr)] + get; } /// <summary><para><c>AddSelector</c> method of <c>IWSManResourceLocator</c> interface. </para><para>Add selector to resource locator</para></summary> @@ -818,14 +819,16 @@ string FragmentDialect int MustUnderstandOptions { - // IDL: HRESULT MustUnderstandOptions ([out, retval] long* ReturnValue); - - [DispId(7)] - get; // IDL: HRESULT MustUnderstandOptions (long value); + [SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1212:PropertyAccessorsMustFollowOrder", Justification = "COM interface defines put_ before get_.")] [DispId(7)] set; + + // IDL: HRESULT MustUnderstandOptions ([out, retval] long* ReturnValue); + + [DispId(7)] + get; } /// <summary><para><c>ClearOptions</c> method of <c>IWSManResourceLocator</c> interface. </para><para>Clear all options</para></summary> diff --git a/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj b/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj index c466ac677fa..d7b6d1235da 100644 --- a/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj +++ b/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj @@ -10,7 +10,7 @@ <ProjectReference Include="..\System.Management.Automation\System.Management.Automation.csproj" /> <ProjectReference Include="..\Microsoft.WSMan.Runtime\Microsoft.WSMan.Runtime.csproj" /> <!-- the following package(s) are from https://github.com/dotnet/corefx --> - <PackageReference Include="System.ServiceProcess.ServiceController" Version="10.0.0-preview.6.25358.103" /> + <PackageReference Include="System.ServiceProcess.ServiceController" Version="11.0.0-preview.3.26207.106" /> </ItemGroup> <PropertyGroup> diff --git a/src/Microsoft.WSMan.Management/WsManHelper.cs b/src/Microsoft.WSMan.Management/WsManHelper.cs index 482f24aa9c1..9249c7f4b88 100644 --- a/src/Microsoft.WSMan.Management/WsManHelper.cs +++ b/src/Microsoft.WSMan.Management/WsManHelper.cs @@ -22,7 +22,7 @@ namespace Microsoft.WSMan.Management { [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "0#")] - internal class WSManHelper + internal sealed class WSManHelper { // regular expressions private const string PTRN_URI_LAST = @"([a-z_][-a-z0-9._]*)$"; @@ -89,7 +89,7 @@ internal class WSManHelper // // // Below class is just a static container which would release sessions in case this DLL is unloaded. - internal class Sessions + internal sealed class Sessions { /// <summary> /// Dictionary object to store the connection. diff --git a/src/Modules/PSGalleryModules.csproj b/src/Modules/PSGalleryModules.csproj index b90e537ca21..3903b164b09 100644 --- a/src/Modules/PSGalleryModules.csproj +++ b/src/Modules/PSGalleryModules.csproj @@ -5,7 +5,7 @@ <Company>Microsoft Corporation</Company> <Copyright>(c) Microsoft Corporation.</Copyright> - <TargetFramework>net10.0</TargetFramework> + <TargetFramework>net11.0</TargetFramework> <SuppressNETCoreSdkPreviewMessage>true</SuppressNETCoreSdkPreviewMessage> </PropertyGroup> @@ -13,11 +13,10 @@ <ItemGroup> <PackageReference Include="PowerShellGet" Version="2.2.5" /> <PackageReference Include="PackageManagement" Version="1.4.8.1" /> - <PackageReference Include="Microsoft.PowerShell.PSResourceGet" Version="1.1.1" /> + <PackageReference Include="Microsoft.PowerShell.PSResourceGet" Version="1.2.0" /> <PackageReference Include="Microsoft.PowerShell.Archive" Version="1.2.5" /> - <PackageReference Include="PSReadLine" Version="2.3.6" /> - <PackageReference Include="ThreadJob" Version="2.1.0" /> - <PackageReference Include="Microsoft.PowerShell.ThreadJob" Version="2.2.0" /> + <PackageReference Include="PSReadLine" Version="2.4.5" /> + <PackageReference Include="Microsoft.PowerShell.ThreadJob" Version="2.2.0" /> </ItemGroup> </Project> diff --git a/src/Modules/Windows/PSDiagnostics/PSDiagnostics.psm1 b/src/Modules/Windows/PSDiagnostics/PSDiagnostics.psm1 index ce0739eb622..c31e9f25963 100644 --- a/src/Modules/Windows/PSDiagnostics/PSDiagnostics.psm1 +++ b/src/Modules/Windows/PSDiagnostics/PSDiagnostics.psm1 @@ -4,21 +4,22 @@ <# PowerShell Diagnostics Module This module contains a set of wrapper scripts that - enable a user to use ETW tracing in Windows - PowerShell. + enable a user to use ETW tracing in PowerShell 7. #> -$script:Logman="$env:windir\system32\logman.exe" -$script:wsmanlogfile = "$env:windir\system32\wsmtraces.log" -$script:wsmprovfile = "$env:windir\system32\wsmtraceproviders.txt" +$script:windir = [System.Environment]::GetEnvironmentVariable("windir", [System.EnvironmentVariableTarget]::Machine) + +$script:Logman = "${script:windir}\system32\logman.exe" +$script:wsmanlogfile = "${script:windir}\system32\wsmtraces.log" +$script:wsmprovfile = "${script:windir}\system32\wsmtraceproviders.txt" $script:wsmsession = "wsmlog" $script:pssession = "PSTrace" -$script:psprovidername="Microsoft-Windows-PowerShell" +$script:psprovidername = "PowerShellCore" $script:wsmprovidername = "Microsoft-Windows-WinRM" $script:oplog = "/Operational" -$script:analyticlog="/Analytic" -$script:debuglog="/Debug" -$script:wevtutil="$env:windir\system32\wevtutil.exe" +$script:analyticlog = "/Analytic" +$script:debuglog = "/Debug" +$script:wevtutil = "${script:windir}\system32\wevtutil.exe" $script:slparam = "sl" $script:glparam = "gl" @@ -169,7 +170,6 @@ function Enable-PSWSManCombinedTrace $provfile = [io.path]::GetTempFilename() - $traceFileName = [string][Guid]::NewGuid() if ($DoNotOverwriteExistingTrace) { $fileName = [string][guid]::newguid() $logfile = $PSHOME + "\\Traces\\PSTrace_$fileName.etl" @@ -177,8 +177,8 @@ function Enable-PSWSManCombinedTrace $logfile = $PSHOME + "\\Traces\\PSTrace.etl" } - "Microsoft-Windows-PowerShell 0 5" | Out-File $provfile -Encoding ascii - "Microsoft-Windows-WinRM 0 5" | Out-File $provfile -Encoding ascii -Append + "$script:psprovidername 0 5" | Out-File $provfile -Encoding ascii + "$script:wsmprovidername 0 5" | Out-File $provfile -Encoding ascii -Append if (!(Test-Path $PSHOME\Traces)) { @@ -192,7 +192,7 @@ function Enable-PSWSManCombinedTrace Start-Trace -SessionName $script:pssession -OutputFilePath $logfile -ProviderFilePath $provfile -ETS - Remove-Item $provfile -Force -ea 0 + Remove-Item $provfile -Force -ErrorAction SilentlyContinue } function Disable-PSWSManCombinedTrace diff --git a/src/PowerShell.Core.Instrumentation/PowerShell.Core.Instrumentation.man b/src/PowerShell.Core.Instrumentation/PowerShell.Core.Instrumentation.man index fb221cfe964..bb4e15351e5 100644 --- a/src/PowerShell.Core.Instrumentation/PowerShell.Core.Instrumentation.man +++ b/src/PowerShell.Core.Instrumentation/PowerShell.Core.Instrumentation.man @@ -121,6 +121,18 @@ value="0x3002" version="1" /> + <!--Telemetry events--> + <event + channel="C_OPERATIONAL" + level="win:Error" + message="$(string.PS_PROVIDER.event.E_O_TelemetrySettingError.message)" + opcode="Exception" + symbol="TelemetrySettingError" + task="Telemetry" + template="T_TelemetrySettingError" + value="0x3011" + version="1" + /> <!--M3P events--> <event channel="C_ANALYTIC" @@ -2208,17 +2220,41 @@ value="0x6017" version="1" /> - <event - channel="C_ANALYTIC" - keywords="AmsiState" - level="win:Verbose" - message="$(string.PS_PROVIDER.event.E_A_AmsiState.message)" - opcode="Method" - symbol="AmsiState" - task="Amsi" - template="T_AmsiState" - value="0x4001" - version="1" + <event + channel="C_ANALYTIC" + keywords="AmsiState" + level="win:Verbose" + message="$(string.PS_PROVIDER.event.E_A_AmsiState.message)" + opcode="Method" + symbol="AmsiState" + task="Amsi" + template="T_AmsiState" + value="0x4001" + version="1" + /> + <event + channel="C_ANALYTIC" + keywords="WDACQuery" + level="win:Verbose" + message="$(string.PS_PROVIDER.event.E_A_WDACQuery.message)" + opcode="Method" + symbol="WDACQuery" + task="WDAC" + template="T_WDACQuery" + value="0x4002" + version="1" + /> + <event + channel="C_ANALYTIC" + keywords="WDACAudit" + level="win:Verbose" + message = "$(string.PS_PROVIDER.event.E_A_WDACAudit.message)" + opcode="Method" + symbol="WDACAudit" + task="WDACAudit" + template="T_WDACAudit" + value="0x4003" + version="1" /> </events> <channels> @@ -2409,6 +2445,12 @@ symbol="T_EXPERIMENTALFEATURE" value="107" /> + <task + message="$(string.PS_PROVIDER.task.T_Telemetry.message)" + name="Telemetry" + symbol="T_TELEMETRY" + value="108" + /> <task message="$(string.PS_PROVIDER.task.T_ScheduledJob.message)" name="ScheduledJob" @@ -2427,11 +2469,23 @@ symbol="T_ISEOperation" value="120" /> - <task - message="$(string.PS_PROVIDER.task.T_AmsiState.message)" - name="Amsi" - symbol="T_Amsi" - value="130" + <task + message="$(string.PS_PROVIDER.task.T_AmsiState.message)" + name="Amsi" + symbol="T_Amsi" + value="130" + /> + <task + message="$(string.PS_PROVIDER.task.T_WDACQuery.message)" + name="WDAC" + symbol="T_WDAC" + value="131" + /> + <task + message="$(string.PS_PROVIDER.task.T_WDACAudit.message)" + name="WDACAudit" + symbol="T_WDACAudit" + value="132" /> </tasks> <opcodes> @@ -2593,11 +2647,23 @@ name="PSWorkflow" symbol="K_PSWORKFLOW" /> - <keyword - mask="0x400" - message="$(string.PS_PROVIDER.keyword.K_AmsiState.message)" - name="AmsiState" - symbol="K_AmsiState" + <keyword + mask="0x400" + message="$(string.PS_PROVIDER.keyword.K_AmsiState.message)" + name="AmsiState" + symbol="K_AmsiState" + /> + <keyword + mask="0x800" + message="$(string.PS_PROVIDER.keyword.K_WDACQuery.message)" + name="WDACQuery" + symbol="K_WDACQuery" + /> + <keyword + mask="0x1000" + message="$(string.PS_PROVIDER.keyword.K_WDACAudit.message)" + name="WDACAudit" + symbol="K_WDACAudit" /> </keywords> <maps> @@ -4004,6 +4070,20 @@ name="StackTrace" /> </template> + <template tid="T_TelemetrySettingError"> + <data + inType="win:UnicodeString" + name="Name" + /> + <data + inType="win:UnicodeString" + name="Message" + /> + <data + inType="win:UnicodeString" + name="StackTrace" + /> + </template> <template tid="T_TrackingGuid"> <data inType="win:GUID" @@ -4080,16 +4160,48 @@ name="FileName" /> </template> - <template tid="T_AmsiState"> - <data - inType="win:UnicodeString" - name="Action" + <template tid="T_AmsiState"> + <data + inType="win:UnicodeString" + name="Action" /> - <data - inType="win:UnicodeString" - name="AmsiContext" + <data + inType="win:UnicodeString" + name="AmsiContext" /> - </template> + </template> + <template tid="T_WDACQuery"> + <data + inType="win:UnicodeString" + name="QueryName" + /> + <data + inType="win:UnicodeString" + name="FileName" + /> + <data + inType="win:Int32" + name="QuerySuccess" + /> + <data + inType="win:Int32" + name="QuerySResult" + /> + </template> + <template tid="T_WDACAudit"> + <data + inType="win:UnicodeString" + name="Title" + /> + <data + inType="win:UnicodeString" + name="Message" + /> + <data + inType="win:UnicodeString" + name="FullyQualifiedId" + /> + </template> </templates> </provider> </events> @@ -5535,6 +5647,14 @@ id="PS_PROVIDER.task.T_ExperimentalFeature.message" value="PowerShell Experimental Features" /> + <string + id="PS_PROVIDER.event.E_O_TelemetrySettingError.message" + value="Failed to retrieve diagnostics and feedback setting from Windows.%n Exception: %1 %n Message: %2 %n StackTrace: %3 %n" + /> + <string + id="PS_PROVIDER.task.T_Telemetry.message" + value="PowerShell Telemetry" + /> <string id="PS_PROVIDER.task.T_NamedPipe.message" value="PowerShell Named Pipe IPC" @@ -5719,6 +5839,30 @@ id="PS_PROVIDER.event.E_O_REMOTE_NAMEDPIPE_DISCONNECT.message" value="PowerShell IPC disconnect on process: %1 in AppDomain: %2 for User: %3." /> + <string + id="PS_PROVIDER.event.E_A_WDACQuery.message" + value="WDAC Query. %n %t Query: %1 %n %t File: %2 %n %t SuccessCode: %3 %n %t ResultCode: %4" + /> + <string + id="PS_PROVIDER.keyword.K_WDACQuery.message" + value="WDAC Query" + /> + <string + id="PS_PROVIDER.task.T_WDACQuery.message" + value="WDAC Query" + /> + <string + id="PS_PROVIDER.event.E_A_WDACAudit.message" + value="WDAC Audit. %n %t Title: %1 %n %t Message: %2 %n %t FullyQualifiedId: %3" + /> + <string + id="PS_PROVIDER.keyword.K_WDACAudit.message" + value="WDAC Audit" + /> + <string + id="PS_PROVIDER.task.T_WDACAudit.message" + value="WDAC Audit" + /> </stringTable> </resources> </localization> diff --git a/src/PowerShell.Core.Instrumentation/README.md b/src/PowerShell.Core.Instrumentation/README.md index 65c8d19fbcb..4701116036b 100644 --- a/src/PowerShell.Core.Instrumentation/README.md +++ b/src/PowerShell.Core.Instrumentation/README.md @@ -1,5 +1,7 @@ # PowerShell.Core.Instrumentation -The code under `PowerShell.Core.Instrumentation` is being migrated to [PowerShell-native](https://github.com/PowerShell/PowerShell-native) repository. -Please make PRs to the new repository. -Code under here will be removed once the move is complete. \ No newline at end of file +The `PowerShell.Core.Instrumentation.man` has actually been migrated to [PowerShell-Native](https://github.com/PowerShell/PowerShell-Native/tree/master/src/PowerShell.Core.Instrumentation) repository. +The corresponding manifest resource DLL `PowerShell.Core.Instrumentation.dll` is now produced in the release build of `PowerShell-Native` and is shipped in the `Microsoft.PowerShell.Native` NuGet package. + +However, we still need to keep `PowerShell.Core.Instrumentation.man` in the PowerShell repository because the PowerShell packages for Windows ship the manifest file and `RegisterManifest.ps1` for users to register the manifest resource DLL if they want to. +Therefore, when making changes to `PowerShell.Core.Instrumentation.man`, make sure your changes are made to both repositories -- **this file needs to be kept in sync between those 2 repositories**. diff --git a/src/ResGen/Program.cs b/src/ResGen/Program.cs index 3359226a99e..a69fd05e64f 100644 --- a/src/ResGen/Program.cs +++ b/src/ResGen/Program.cs @@ -52,7 +52,7 @@ public static void Main(string[] args) if (className.StartsWith("public.", StringComparison.InvariantCultureIgnoreCase)) { accessModifier = "public"; - className = className.Substring(className.IndexOf(".") + 1); + className = className.Substring(className.IndexOf('.') + 1); } string sourceCode = GetStronglyTypeCsFileForResx(resxPath, moduleName, className, accessModifier); diff --git a/src/ResGen/ResGen.csproj b/src/ResGen/ResGen.csproj index 7fcf1ff3f35..954038cfa51 100644 --- a/src/ResGen/ResGen.csproj +++ b/src/ResGen/ResGen.csproj @@ -2,7 +2,7 @@ <PropertyGroup> <Description>Generates C# typed bindings for .resx files</Description> - <TargetFramework>net10.0</TargetFramework> + <TargetFramework>net11.0</TargetFramework> <AssemblyName>resgen</AssemblyName> <OutputType>Exe</OutputType> <TieredCompilation>true</TieredCompilation> diff --git a/src/System.Management.Automation/AssemblyInfo.cs b/src/System.Management.Automation/AssemblyInfo.cs index 559d809fe72..e265bb453c8 100644 --- a/src/System.Management.Automation/AssemblyInfo.cs +++ b/src/System.Management.Automation/AssemblyInfo.cs @@ -7,6 +7,7 @@ [assembly: InternalsVisibleTo("powershell-tests,PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: InternalsVisibleTo("powershell-perf,PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] +[assembly: InternalsVisibleTo("powershell-fuzz-tests,PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: InternalsVisibleTo(@"Microsoft.PowerShell.Commands.Utility" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] [assembly: InternalsVisibleTo(@"Microsoft.PowerShell.Commands.Management" + @",PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] diff --git a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs index dc5db5f2c48..4cbb346fb14 100644 --- a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs +++ b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs @@ -167,16 +167,24 @@ public static bool IsStaSupported internal static readonly string ConfigDirectory = Platform.SelectProductNameForDirectory(Platform.XDG_Type.CONFIG); #else // Gets the location for cache and config folders. - internal static readonly string CacheDirectory = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + @"\Microsoft\PowerShell"; - internal static readonly string ConfigDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Personal) + @"\PowerShell"; + internal static readonly string CacheDirectory = SafeDeriveFromSpecialFolder( + Environment.SpecialFolder.LocalApplicationData, + @"Microsoft\PowerShell"); + + internal static readonly string ConfigDirectory = SafeDeriveFromSpecialFolder( + Environment.SpecialFolder.Personal, + @"PowerShell"); private static readonly Lazy<bool> _isStaSupported = new Lazy<bool>(() => { int result = Interop.Windows.CoInitializeEx(IntPtr.Zero, Interop.Windows.COINIT_APARTMENTTHREADED); - // If 0 is returned the thread has been initialized for the first time - // as an STA and thus supported and needs to be uninitialized. - if (result > 0) + // Per COM documentation: Each successful call to CoInitializeEx (including S_FALSE) + // must be balanced by a corresponding call to CoUninitialize. + // - S_OK (0) means we initialized for the first time. + // - S_FALSE (1) means already initialized, but still increments the reference count. + // Both require CoUninitialize to decrement the reference count. + if (result >= 0) { Interop.Windows.CoUninitialize(); } @@ -189,6 +197,30 @@ public static bool IsStaSupported private static bool? _isWindowsDesktop = null; #endif + internal static bool TryDeriveFromCache(string path1, out string result) + { + if (CacheDirectory is null or []) + { + result = null; + return false; + } + + result = Path.Combine(CacheDirectory, path1); + return true; + } + + internal static bool TryDeriveFromCache(string path1, string path2, out string result) + { + if (CacheDirectory is null or []) + { + result = null; + return false; + } + + result = Path.Combine(CacheDirectory, path1, path2); + return true; + } + // format files internal static readonly string[] FormatFileNames = new string[] { @@ -218,6 +250,17 @@ internal static class CommonEnvVariableNames #endif } + private static string SafeDeriveFromSpecialFolder(Environment.SpecialFolder specialFolder, string subPath) + { + string basePath = Environment.GetFolderPath(specialFolder, Environment.SpecialFolderOption.DoNotVerify); + if (string.IsNullOrWhiteSpace(basePath)) + { + return string.Empty; + } + + return Path.Join(basePath, subPath); + } + #if UNIX private static string s_tempHome = null; @@ -360,7 +403,7 @@ internal static string GetFolderPath(Environment.SpecialFolder folder) _ => throw new NotSupportedException() }; #else - return Environment.GetFolderPath(folder); + return Environment.GetFolderPath(folder, Environment.SpecialFolderOption.DoNotVerify); #endif } @@ -637,25 +680,29 @@ public string GetModeString() return DirectoryOwnerFullGroupReadExecOtherReadExec; } - Span<char> modeCharacters = stackalloc char[10]; - modeCharacters[0] = itemTypeTable[ItemType]; - bool isExecutable; - UnixFileMode modeInfo = (UnixFileMode)Mode; - modeCharacters[1] = modeInfo.HasFlag(UnixFileMode.UserRead) ? CanRead : NoPerm; - modeCharacters[2] = modeInfo.HasFlag(UnixFileMode.UserWrite) ? CanWrite : NoPerm; - isExecutable = modeInfo.HasFlag(UnixFileMode.UserExecute); - modeCharacters[3] = modeInfo.HasFlag(UnixFileMode.SetUser) ? (isExecutable ? SetAndExec : SetAndNotExec) : (isExecutable ? CanExecute : NoPerm); - - modeCharacters[4] = modeInfo.HasFlag(UnixFileMode.GroupRead) ? CanRead : NoPerm; - modeCharacters[5] = modeInfo.HasFlag(UnixFileMode.GroupWrite) ? CanWrite : NoPerm; - isExecutable = modeInfo.HasFlag(UnixFileMode.GroupExecute); - modeCharacters[6] = modeInfo.HasFlag(UnixFileMode.SetGroup) ? (isExecutable ? SetAndExec : SetAndNotExec) : (isExecutable ? CanExecute : NoPerm); - - modeCharacters[7] = modeInfo.HasFlag(UnixFileMode.OtherRead) ? CanRead : NoPerm; - modeCharacters[8] = modeInfo.HasFlag(UnixFileMode.OtherWrite) ? CanWrite : NoPerm; - isExecutable = modeInfo.HasFlag(UnixFileMode.OtherExecute); - modeCharacters[9] = modeInfo.HasFlag(UnixFileMode.StickyBit) ? (isExecutable ? StickyAndExec : StickyAndNotExec) : (isExecutable ? CanExecute : NoPerm); + + Span<char> modeCharacters = [ + itemTypeTable[ItemType], + + modeInfo.HasFlag(UnixFileMode.UserRead) ? CanRead : NoPerm, + modeInfo.HasFlag(UnixFileMode.UserWrite) ? CanWrite : NoPerm, + modeInfo.HasFlag(UnixFileMode.SetUser) ? + (modeInfo.HasFlag(UnixFileMode.UserExecute) ? SetAndExec : SetAndNotExec) : + (modeInfo.HasFlag(UnixFileMode.UserExecute) ? CanExecute : NoPerm), + + modeInfo.HasFlag(UnixFileMode.GroupRead) ? CanRead : NoPerm, + modeInfo.HasFlag(UnixFileMode.GroupWrite) ? CanWrite : NoPerm, + modeInfo.HasFlag(UnixFileMode.SetGroup) ? + (modeInfo.HasFlag(UnixFileMode.GroupExecute) ? SetAndExec : SetAndNotExec) : + (modeInfo.HasFlag(UnixFileMode.GroupExecute) ? CanExecute : NoPerm), + + modeInfo.HasFlag(UnixFileMode.OtherRead) ? CanRead : NoPerm, + modeInfo.HasFlag(UnixFileMode.OtherWrite) ? CanWrite : NoPerm, + modeInfo.HasFlag(UnixFileMode.StickyBit) ? + (modeInfo.HasFlag(UnixFileMode.OtherExecute) ? StickyAndExec : StickyAndNotExec) : + (modeInfo.HasFlag(UnixFileMode.OtherExecute) ? CanExecute : NoPerm), + ]; return new string(modeCharacters); } diff --git a/src/System.Management.Automation/DscSupport/CimDSCParser.cs b/src/System.Management.Automation/DscSupport/CimDSCParser.cs index 6160577beb2..283ec1bcad8 100644 --- a/src/System.Management.Automation/DscSupport/CimDSCParser.cs +++ b/src/System.Management.Automation/DscSupport/CimDSCParser.cs @@ -525,9 +525,6 @@ public DscClassCacheEntry(DSCResourceRunAsCredential aDSCResourceRunAsCredential public static class DscClassCache { private const string InboxDscResourceModulePath = "WindowsPowershell\\v1.0\\Modules\\PsDesiredStateConfiguration"; - private const string reservedDynamicKeywords = "^(Synchronization|Certificate|IIS|SQL)$"; - - private const string reservedProperties = "^(Require|Trigger|Notify|Before|After|Subscribe)$"; private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("DSC", "DSC Class Cache"); @@ -1264,13 +1261,6 @@ public static void ValidateInstanceText(string instanceText) parser.ValidateInstanceText(instanceText); } - private static bool IsMagicProperty(string propertyName) - { - return System.Text.RegularExpressions.Regex.Match(propertyName, - "^(ResourceId|SourceInfo|ModuleName|ModuleVersion|ConfigurationName)$", - System.Text.RegularExpressions.RegexOptions.IgnoreCase).Success; - } - private static string GetFriendlyName(CimClass cimClass) { try @@ -1367,7 +1357,8 @@ private static DynamicKeyword CreateKeywordFromCimClass(string moduleName, Versi // // Skip all of the base, meta, registration and other classes that are not intended to be used directly by a script author // - if (System.Text.RegularExpressions.Regex.Match(keywordString, "^OMI_Base|^OMI_.*Registration", System.Text.RegularExpressions.RegexOptions.IgnoreCase).Success) + if (keywordString.StartsWith("OMI_Base", StringComparison.OrdinalIgnoreCase) || + (keywordString.StartsWith("OMI_", StringComparison.OrdinalIgnoreCase) && keywordString.IndexOf("Registration", 4, StringComparison.OrdinalIgnoreCase) >= 0)) { return null; } @@ -1383,7 +1374,7 @@ private static DynamicKeyword CreateKeywordFromCimClass(string moduleName, Versi }; // If it's one of reserved dynamic keyword, mark it - if (System.Text.RegularExpressions.Regex.Match(keywordString, reservedDynamicKeywords, System.Text.RegularExpressions.RegexOptions.IgnoreCase).Success) + if (IsReservedDynamicKeyword(keywordString)) { keyword.IsReservedKeyword = true; } @@ -1441,7 +1432,7 @@ private static DynamicKeyword CreateKeywordFromCimClass(string moduleName, Versi } } // If it's one of our reserved properties, save it for error reporting - if (System.Text.RegularExpressions.Regex.Match(prop.Name, reservedProperties, System.Text.RegularExpressions.RegexOptions.IgnoreCase).Success) + if (IsReservedProperty(prop.Name)) { keyword.HasReservedProperties = true; continue; @@ -1540,6 +1531,27 @@ private static DynamicKeyword CreateKeywordFromCimClass(string moduleName, Versi UpdateKnownRestriction(keyword); return keyword; + + static bool IsMagicProperty(string propertyName) => + string.Equals(propertyName, "ResourceId", StringComparison.OrdinalIgnoreCase) || + string.Equals(propertyName, "SourceInfo", StringComparison.OrdinalIgnoreCase) || + string.Equals(propertyName, "ModuleName", StringComparison.OrdinalIgnoreCase) || + string.Equals(propertyName, "ModuleVersion", StringComparison.OrdinalIgnoreCase) || + string.Equals(propertyName, "ConfigurationName", StringComparison.OrdinalIgnoreCase); + + static bool IsReservedDynamicKeyword(string keyword) => + string.Equals(keyword, "Synchronization", StringComparison.OrdinalIgnoreCase) || + string.Equals(keyword, "Certificate", StringComparison.OrdinalIgnoreCase) || + string.Equals(keyword, "IIS", StringComparison.OrdinalIgnoreCase) || + string.Equals(keyword, "SQL", StringComparison.OrdinalIgnoreCase); + + static bool IsReservedProperty(string name) => + string.Equals(name, "Require", StringComparison.OrdinalIgnoreCase) || + string.Equals(name, "Trigger", StringComparison.OrdinalIgnoreCase) || + string.Equals(name, "Notify", StringComparison.OrdinalIgnoreCase) || + string.Equals(name, "Before", StringComparison.OrdinalIgnoreCase) || + string.Equals(name, "After", StringComparison.OrdinalIgnoreCase) || + string.Equals(name, "Subscribe", StringComparison.OrdinalIgnoreCase); } /// <summary> @@ -3252,7 +3264,7 @@ public static bool ImportCimKeywordsFromModule(PSModuleInfo module, string resou string tempSchemaFilepath = schemaFiles.FirstOrDefault(); Debug.Assert(schemaFiles.Count() == 1, "A valid DSCResource module can have only one schema mof file"); - + if (tempSchemaFilepath is not null) { var classes = GetCachedClassByFileName(tempSchemaFilepath) ?? ImportClasses(tempSchemaFilepath, new Tuple<string, Version>(module.Name, module.Version), errors); diff --git a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/FileSystem_format_ps1xml.cs b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/FileSystem_format_ps1xml.cs index 0400f99b899..4c8d23971af 100644 --- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/FileSystem_format_ps1xml.cs +++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/FileSystem_format_ps1xml.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.Collections.Generic; +using System.Globalization; namespace System.Management.Automation.Runspaces { @@ -48,7 +49,10 @@ private static IEnumerable<FormatViewDefinition> ViewsOf_FileSystemTypes(CustomC .AddHeader(Alignment.Left, label: "UnixMode", width: 10) .AddHeader(Alignment.Right, label: "User", width: 10) .AddHeader(Alignment.Left, label: "Group", width: 10) - .AddHeader(Alignment.Right, label: "LastWriteTime", width: 16) + .AddHeader( + Alignment.Right, + label: "LastWriteTime", + width: String.Format(CultureInfo.CurrentCulture, "{0:d} {0:HH}:{0:mm}", CultureInfo.CurrentCulture.Calendar.MaxSupportedDateTime).Length) .AddHeader(Alignment.Right, label: "Size", width: 12) .AddHeader(Alignment.Left, label: "Name") .StartRowDefinition(wrap: true) diff --git a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Help_format_ps1xml.cs b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Help_format_ps1xml.cs index 05f57286026..6825ec14e6c 100644 --- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Help_format_ps1xml.cs +++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/Help_format_ps1xml.cs @@ -680,10 +680,7 @@ private static IEnumerable<FormatViewDefinition> ViewsOf_MamlCommandHelpInfo_Ful .StartFrame(leftIndent: 4) .AddPropertyExpressionBinding(@"title") .AddNewline() - .AddNewline() - .StartFrame(leftIndent: 4) - .AddPropertyExpressionBinding(@"alert", enumerateCollection: true, customControl: sharedControls[11]) - .EndFrame() + .AddPropertyExpressionBinding(@"alert", enumerateCollection: true, customControl: sharedControls[11]) .AddNewline() .EndFrame() .EndEntry() diff --git a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs index ef0d506d122..24d99d4dd4b 100644 --- a/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs +++ b/src/System.Management.Automation/FormatAndOutput/DefaultFormatters/PowerShellCore_format_ps1xml.cs @@ -1190,8 +1190,37 @@ function Get-ConciseViewPositionMessage { $highlightLine = '' if ($useTargetObject) { $line = $_.TargetObject.LineText.Trim() + $offsetLength = 0 $offsetInLine = 0 + $startColumn = 0 + if ( + ([System.Collections.IDictionary]$_.TargetObject).Contains('StartColumn') -and + [System.Management.Automation.LanguagePrimitives]::TryConvertTo[int]($_.TargetObject.StartColumn, [ref]$startColumn) -and + $null -ne $startColumn -and + $startColumn -gt 0 -and + $startColumn -le $line.Length + ) { + $endColumn = 0 + if (-not ( + ([System.Collections.IDictionary]$_.TargetObject).Contains('EndColumn') -and + [System.Management.Automation.LanguagePrimitives]::TryConvertTo[int]($_.TargetObject.EndColumn, [ref]$endColumn) -and + $null -ne $endColumn -and + $endColumn -gt $startColumn -and + $endColumn -le ($line.Length + 1) + )) { + $endColumn = $line.Length + 1 + } + + # Input is expected to be 1-based index to match the extent positioning + # but we use 0-based indexing below. + $startColumn -= 1 + $endColumn -= 1 + + $highlightLine = "$(" " * $startColumn)$("~" * ($endColumn - $startColumn))" + $offsetLength = $endColumn - $startColumn + $offsetInLine = $startColumn + } } else { $positionMessage = $myinv.PositionMessage.Split($newline) diff --git a/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommand.cs b/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommand.cs index 18b74cbcfe1..be31a1db9a8 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommand.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommand.cs @@ -9,6 +9,7 @@ using System.Management.Automation; using System.Management.Automation.Internal; using System.Management.Automation.Runspaces; +using Microsoft.PowerShell.Commands; namespace Microsoft.PowerShell.Commands.Internal.Format { @@ -727,8 +728,15 @@ public class OuterFormatTableAndListBase : OuterFormatShapeCommandBase /// will be determined using property sets, etc. /// </summary> [Parameter(Position = 0)] + [ValidateNotNullOrEmpty] public object[] Property { get; set; } + /// <summary> + /// Optional parameter for excluding properties from formatting. + /// </summary> + [Parameter] + public string[] ExcludeProperty { get; set; } + #endregion internal override FormattingCommandLineParameters GetCommandLineParameters() @@ -751,6 +759,18 @@ internal override FormattingCommandLineParameters GetCommandLineParameters() internal void GetCommandLineProperties(FormattingCommandLineParameters parameters, bool isTable) { + // Check View conflicts first (before any auto-expansion) + if (!string.IsNullOrEmpty(this.View)) + { + // View cannot be used with Property or ExcludeProperty + if ((Property is not null && Property.Length != 0) || (ExcludeProperty is not null && ExcludeProperty.Length != 0)) + { + ReportCannotSpecifyViewAndProperty(); + } + + parameters.viewName = this.View; + } + if (Property != null) { CommandParameterDefinition def; @@ -765,15 +785,21 @@ internal void GetCommandLineProperties(FormattingCommandLineParameters parameter parameters.mshParameterList = processor.ProcessParameters(Property, invocationContext); } - if (!string.IsNullOrEmpty(this.View)) + if (ExcludeProperty is not null) { - // we have a view command line switch - if (parameters.mshParameterList.Count != 0) + parameters.excludePropertyFilter = new PSPropertyExpressionFilter(ExcludeProperty); + + // ExcludeProperty implies -Property * for better UX + if (Property is null || Property.Length == 0) { - ReportCannotSpecifyViewAndProperty(); - } + CommandParameterDefinition def = isTable + ? new FormatTableParameterDefinition() + : new FormatListParameterDefinition(); + ParameterProcessor processor = new ParameterProcessor(def); + TerminatingErrorContext invocationContext = new TerminatingErrorContext(this); - parameters.viewName = this.View; + parameters.mshParameterList = processor.ProcessParameters(new object[] { "*" }, invocationContext); + } } } } diff --git a/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommandParameters.cs b/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommandParameters.cs index 7f88727ace7..498f6098a24 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommandParameters.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/BaseFormattingCommandParameters.cs @@ -70,6 +70,11 @@ internal sealed class FormattingCommandLineParameters /// Extension mechanism for shape specific parameters. /// </summary> internal ShapeSpecificParameters shapeParameters = null; + + /// <summary> + /// Filter for excluding properties from formatting. + /// </summary> + internal PSPropertyExpressionFilter excludePropertyFilter = null; } /// <summary> diff --git a/src/System.Management.Automation/FormatAndOutput/common/ComplexWriter.cs b/src/System.Management.Automation/FormatAndOutput/common/ComplexWriter.cs index 61a3f962daf..a69ad41c965 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/ComplexWriter.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/ComplexWriter.cs @@ -9,7 +9,6 @@ using System.Management.Automation; using System.Management.Automation.Internal; using System.Text; -using System.Text.RegularExpressions; namespace Microsoft.PowerShell.Commands.Internal.Format { @@ -317,6 +316,7 @@ internal struct GetWordsResult { internal string Word; internal string Delim; + internal bool VtResetAdded; } /// <summary> @@ -367,9 +367,19 @@ private static IEnumerable<GetWordsResult> GetWords(string s) { var vtSpan = s.AsSpan(i, len); sb.Append(vtSpan); - vtSeqs.Append(vtSpan); - wordHasVtSeqs = true; + if (vtSpan.SequenceEqual(PSStyle.Instance.Reset)) + { + // The Reset sequence will void all previous VT sequences. + vtSeqs.Clear(); + wordHasVtSeqs = false; + } + else + { + vtSeqs.Append(vtSpan); + wordHasVtSeqs = true; + } + i += len - 1; continue; } @@ -390,15 +400,18 @@ private static IEnumerable<GetWordsResult> GetWords(string s) if (delimiter is not null) { + bool vtResetAdded = false; if (wordHasVtSeqs && !sb.EndsWith(PSStyle.Instance.Reset)) { + vtResetAdded = true; sb.Append(PSStyle.Instance.Reset); } var result = new GetWordsResult() { Word = sb.ToString(), - Delim = delimiter + Delim = delimiter, + VtResetAdded = vtResetAdded }; sb.Clear().Append(vtSeqs); @@ -611,7 +624,7 @@ private static StringCollection GenerateLinesWithWordWrap(DisplayCells displayCe if (suffix is not null) { - wordToAdd = wordToAdd.EndsWith(resetStr) + wordToAdd = word.VtResetAdded ? wordToAdd.Insert(wordToAdd.Length - resetStr.Length, suffix) : wordToAdd + suffix; } @@ -760,9 +773,19 @@ internal static List<string> SplitLines(string s) { var vtSpan = s.AsSpan(i, len); sb.Append(vtSpan); - vtSeqs.Append(vtSpan); - hasVtSeqs = true; + if (vtSpan.SequenceEqual(PSStyle.Instance.Reset)) + { + // The Reset sequence will void all previous VT sequences. + vtSeqs.Clear(); + hasVtSeqs = false; + } + else + { + vtSeqs.Append(vtSpan); + hasVtSeqs = true; + } + i += len - 1; continue; } diff --git a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/FormatTable.cs b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/FormatTable.cs index a1e34424950..2ae4ac2626e 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/FormatTable.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/FormatTable.cs @@ -20,7 +20,7 @@ namespace System.Management.Automation.Runspaces /// <summary> /// This exception is used by Formattable constructor to indicate errors /// occurred during construction time. - /// </summary> + /// </summary> [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "FormatTable")] public class FormatTableLoadException : RuntimeException { diff --git a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataQuery.cs b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataQuery.cs index c759cabd2a1..de6df333951 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataQuery.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/DisplayDatabase/typeDataQuery.cs @@ -606,7 +606,7 @@ internal static AppliesTo GetAllApplicableTypes(TypeInfoDataBase db, AppliesTo a else { // check if we have a type group reference - if (!(r is TypeGroupReference tgr)) + if (r is not TypeGroupReference tgr) continue; // find the type group definition the reference points to diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator.cs b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator.cs index db1bc0704aa..963b5a0f88b 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.Collections.Generic; +using System.Linq; using System.Collections.ObjectModel; using System.Management.Automation; using System.Management.Automation.Internal; @@ -347,8 +348,50 @@ protected class DataBaseInfo protected DataBaseInfo dataBaseInfo = new DataBaseInfo(); - protected List<MshResolvedExpressionParameterAssociation> activeAssociationList = null; - protected FormattingCommandLineParameters inputParameters = null; + /// <summary> + /// Builds the raw association list for the given object. + /// Subclasses override this to provide cmdlet-specific property expansion logic. + /// </summary> + /// <param name="so">The object to build the association list for.</param> + /// <param name="propertyList">The list of properties specified by the user, or null if not specified.</param> + /// <returns>The raw association list, or null if not applicable.</returns> + protected virtual List<MshResolvedExpressionParameterAssociation> BuildRawAssociationList(PSObject so, List<MshParameter> propertyList) + { + return null; + } + + /// <summary> + /// Builds the active association list for the given object, with ExcludeProperty filter applied. + /// </summary> + /// <param name="so">The object to build the association list for.</param> + /// <returns>The filtered association list.</returns> + protected List<MshResolvedExpressionParameterAssociation> BuildActiveAssociationList(PSObject so) + { + var propertyList = parameters?.mshParameterList; + var excludeFilter = parameters?.excludePropertyFilter; + var rawList = BuildRawAssociationList(so, propertyList); + return ApplyExcludeFilter(rawList, excludeFilter); + } + + /// <summary> + /// Applies the ExcludeProperty filter to the given association list. + /// </summary> + /// <param name="associationList">The list to filter.</param> + /// <param name="excludeFilter">The exclude filter to apply.</param> + /// <returns>The filtered list, or the original list if no filter is specified.</returns> + internal static List<MshResolvedExpressionParameterAssociation> ApplyExcludeFilter( + List<MshResolvedExpressionParameterAssociation> associationList, + PSPropertyExpressionFilter excludeFilter) + { + if (associationList is null || excludeFilter is null) + { + return associationList; + } + + return associationList + .Where(item => !excludeFilter.IsMatch(item.ResolvedExpression)) + .ToList(); + } protected string GetExpressionDisplayValue(PSObject so, int enumerationLimit, PSPropertyExpression ex, FieldFormattingDirective directive) diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Complex.cs b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Complex.cs index b2bbd27c2b9..b81c0c0f860 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Complex.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Complex.cs @@ -16,7 +16,6 @@ internal override void Initialize(TerminatingErrorContext errorContext, PSProper PSObject so, TypeInfoDataBase db, FormattingCommandLineParameters parameters) { base.Initialize(errorContext, expressionFactory, so, db, parameters); - this.inputParameters = parameters; } internal override FormatStartData GenerateStartData(PSObject so) @@ -40,7 +39,7 @@ internal override FormatEntryData GeneratePayload(PSObject so, int enumerationLi private ComplexViewEntry GenerateComplexViewEntryFromProperties(PSObject so, int enumerationLimit) { ComplexViewObjectBrowser browser = new ComplexViewObjectBrowser(this.ErrorManager, this.expressionFactory, enumerationLimit); - return browser.GenerateView(so, this.inputParameters); + return browser.GenerateView(so, this.parameters); } private ComplexViewEntry GenerateComplexViewEntryFromDataBaseInfo(PSObject so, int enumerationLimit) @@ -425,17 +424,18 @@ internal ComplexViewObjectBrowser(FormatErrorManager resultErrorManager, PSPrope /// of the object. /// </summary> /// <param name="so">Object to process.</param> - /// <param name="inputParameters">Parameters from the command line.</param> + /// <param name="parameters">Parameters from the command line.</param> /// <returns>Complex view entry to send to the output command.</returns> - internal ComplexViewEntry GenerateView(PSObject so, FormattingCommandLineParameters inputParameters) + internal ComplexViewEntry GenerateView(PSObject so, FormattingCommandLineParameters parameters) { - _complexSpecificParameters = (ComplexSpecificParameters)inputParameters.shapeParameters; + _parameters = parameters; + _complexSpecificParameters = (ComplexSpecificParameters)parameters.shapeParameters; int maxDepth = _complexSpecificParameters.maxDepth; TraversalInfo level = new TraversalInfo(0, maxDepth); List<MshParameter> mshParameterList = null; - mshParameterList = inputParameters.mshParameterList; + mshParameterList = parameters.mshParameterList; // create a top level entry as root of the tree ComplexViewEntry cve = new ComplexViewEntry(); @@ -513,6 +513,9 @@ private void DisplayObject(PSObject so, TraversalInfo currentLevel, List<MshPara List<MshResolvedExpressionParameterAssociation> activeAssociationList = AssociationManager.SetupActiveProperties(parameterList, so, _expressionFactory); + // Apply ExcludeProperty filter using the centralized method + activeAssociationList = ViewGenerator.ApplyExcludeFilter(activeAssociationList, _parameters?.excludePropertyFilter); + // create a format entry FormatEntry fe = new FormatEntry(); formatValueList.Add(fe); @@ -758,6 +761,7 @@ private List<FormatValue> AddIndentationLevel(List<FormatValue> formatValueList) return feFrame.formatValueList; } + private FormattingCommandLineParameters _parameters; private ComplexSpecificParameters _complexSpecificParameters; /// <summary> diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_List.cs b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_List.cs index f7acd9ed226..35287d7c2e4 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_List.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_List.cs @@ -30,9 +30,14 @@ internal override void Initialize(TerminatingErrorContext errorContext, PSProper { _listBody = (ListControlBody)this.dataBaseInfo.view.mainControl; } + } - this.inputParameters = parameters; - SetUpActiveProperties(so); + /// <summary> + /// Builds the raw association list for list formatting. + /// </summary> + protected override List<MshResolvedExpressionParameterAssociation> BuildRawAssociationList(PSObject so, List<MshParameter> propertyList) + { + return AssociationManager.SetupActiveProperties(propertyList, so, this.expressionFactory); } /// <summary> @@ -178,17 +183,14 @@ private ListControlEntryDefinition GetActiveListControlEntryDefinition(ListContr private ListViewEntry GenerateListViewEntryFromProperties(PSObject so, int enumerationLimit) { - // compute active properties every time - if (this.activeAssociationList == null) - { - SetUpActiveProperties(so); - } + // Build active association list (with ExcludeProperty filter applied) + var associationList = BuildActiveAssociationList(so); ListViewEntry lve = new ListViewEntry(); - for (int k = 0; k < this.activeAssociationList.Count; k++) + for (int k = 0; k < associationList.Count; k++) { - MshResolvedExpressionParameterAssociation a = this.activeAssociationList[k]; + MshResolvedExpressionParameterAssociation a = associationList[k]; ListViewField lvf = new ListViewField(); if (a.OriginatingParameter != null) @@ -218,19 +220,7 @@ private ListViewEntry GenerateListViewEntryFromProperties(PSObject so, int enume lvf.formatPropertyField.propertyValue = this.GetExpressionDisplayValue(so, enumerationLimit, a.ResolvedExpression, directive); lve.listViewFieldList.Add(lvf); } - - this.activeAssociationList = null; return lve; } - - private void SetUpActiveProperties(PSObject so) - { - List<MshParameter> mshParameterList = null; - - if (this.inputParameters != null) - mshParameterList = this.inputParameters.mshParameterList; - - this.activeAssociationList = AssociationManager.SetupActiveProperties(mshParameterList, so, this.expressionFactory); - } } } diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Table.cs b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Table.cs index 0ddc307b646..3b14c0754ba 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Table.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Table.cs @@ -14,6 +14,8 @@ internal sealed class TableViewGenerator : ViewGenerator // tableBody to use for this instance of the ViewGenerator; private TableControlBody _tableBody; + private List<MshResolvedExpressionParameterAssociation> _activeAssociationList; + internal override void Initialize(TerminatingErrorContext terminatingErrorContext, PSPropertyExpressionFactory mshExpressionFactory, TypeInfoDataBase db, ViewDefinition view, FormattingCommandLineParameters formatParameters) { base.Initialize(terminatingErrorContext, mshExpressionFactory, db, view, formatParameters); @@ -34,46 +36,48 @@ internal override void Initialize(TerminatingErrorContext errorContext, PSProper _tableBody = (TableControlBody)this.dataBaseInfo.view.mainControl; } - List<MshParameter> rawMshParameterList = null; - - if (parameters != null) - rawMshParameterList = parameters.mshParameterList; + // Build the active association list (with ExcludeProperty filter applied) + _activeAssociationList = BuildActiveAssociationList(so); + } + /// <summary> + /// Builds the raw association list for table formatting. + /// </summary> + protected override List<MshResolvedExpressionParameterAssociation> BuildRawAssociationList(PSObject so, List<MshParameter> propertyList) + { // check if we received properties from the command line - if (rawMshParameterList != null && rawMshParameterList.Count > 0) + if (propertyList is not null && propertyList.Count > 0) { - this.activeAssociationList = AssociationManager.ExpandTableParameters(rawMshParameterList, so); - return; + return AssociationManager.ExpandTableParameters(propertyList, so); } // we did not get any properties: // try to get properties from the default property set of the object - this.activeAssociationList = AssociationManager.ExpandDefaultPropertySet(so, this.expressionFactory); - if (this.activeAssociationList.Count > 0) + var list = AssociationManager.ExpandDefaultPropertySet(so, this.expressionFactory); + if (list.Count > 0) { // we got a valid set of properties from the default property set..add computername for // remoteobjects (if available) if (PSObjectHelper.ShouldShowComputerNameProperty(so)) { - activeAssociationList.Add(new MshResolvedExpressionParameterAssociation(null, + list.Add(new MshResolvedExpressionParameterAssociation(null, new PSPropertyExpression(RemotingConstants.ComputerNameNoteProperty))); } - return; + return list; } // we failed to get anything from the default property set - this.activeAssociationList = AssociationManager.ExpandAll(so); - if (this.activeAssociationList.Count > 0) + list = AssociationManager.ExpandAll(so); + if (list.Count > 0) { // Remove PSComputerName and PSShowComputerName from the display as needed. - AssociationManager.HandleComputerNameProperties(so, activeAssociationList); - FilterActiveAssociationList(); - return; + AssociationManager.HandleComputerNameProperties(so, list); + return LimitAssociationListSize(list); } // we were unable to retrieve any properties, so we leave an empty list - this.activeAssociationList = new List<MshResolvedExpressionParameterAssociation>(); + return new List<MshResolvedExpressionParameterAssociation>(); } /// <summary> @@ -124,30 +128,29 @@ internal override FormatStartData GenerateStartData(PSObject so) } /// <summary> - /// Method to filter resolved expressions as per table view needs. + /// Limits the association list size for table view. /// For v1.0, table view supports only 10 properties. - /// - /// This method filters and updates "activeAssociationList" instance property. /// </summary> - /// <returns>None.</returns> - /// <remarks>This method updates "activeAssociationList" instance property.</remarks> - private void FilterActiveAssociationList() + /// <param name="list">The list to limit.</param> + /// <returns>The limited list.</returns> + private static List<MshResolvedExpressionParameterAssociation> LimitAssociationListSize( + List<MshResolvedExpressionParameterAssociation> list) { - // we got a valid set of properties from the default property set - // make sure we do not have too many properties - // NOTE: this is an arbitrary number, chosen to be a sensitive default - const int nMax = 10; + const int maxCount = 10; + + if (list.Count <= maxCount) + { + return list; + } - if (activeAssociationList.Count > nMax) + var result = new List<MshResolvedExpressionParameterAssociation>(maxCount); + for (int k = 0; k < maxCount; k++) { - List<MshResolvedExpressionParameterAssociation> tmp = this.activeAssociationList; - this.activeAssociationList = new List<MshResolvedExpressionParameterAssociation>(); - for (int k = 0; k < nMax; k++) - this.activeAssociationList.Add(tmp[k]); + result.Add(list[k]); } - return; + return result; } private TableHeaderInfo GenerateTableHeaderInfoFromDataBaseInfo(PSObject so) @@ -223,9 +226,9 @@ private TableHeaderInfo GenerateTableHeaderInfoFromProperties(PSObject so) thi.hideHeader = this.HideHeaders; thi.repeatHeader = this.RepeatHeader; - for (int k = 0; k < this.activeAssociationList.Count; k++) + for (int k = 0; k < _activeAssociationList.Count; k++) { - MshResolvedExpressionParameterAssociation a = this.activeAssociationList[k]; + MshResolvedExpressionParameterAssociation a = _activeAssociationList[k]; TableColumnInfo ci = new TableColumnInfo(); // set the label of the column @@ -236,7 +239,7 @@ private TableHeaderInfo GenerateTableHeaderInfoFromProperties(PSObject so) ci.propertyName = (string)key; } - ci.propertyName ??= this.activeAssociationList[k].ResolvedExpression.ToString(); + ci.propertyName ??= _activeAssociationList[k].ResolvedExpression.ToString(); // set the width of the table if (a.OriginatingParameter != null) @@ -468,13 +471,13 @@ private TableRowEntry GenerateTableRowEntryFromDataBaseInfo(PSObject so, int enu private TableRowEntry GenerateTableRowEntryFromFromProperties(PSObject so, int enumerationLimit) { TableRowEntry tre = new TableRowEntry(); - for (int k = 0; k < this.activeAssociationList.Count; k++) + for (int k = 0; k < _activeAssociationList.Count; k++) { FormatPropertyField fpf = new FormatPropertyField(); FieldFormattingDirective directive = null; - if (activeAssociationList[k].OriginatingParameter != null) + if (_activeAssociationList[k].OriginatingParameter != null) { - directive = activeAssociationList[k].OriginatingParameter.GetEntry(FormatParameterDefinitionKeys.FormatStringEntryKey) as FieldFormattingDirective; + directive = _activeAssociationList[k].OriginatingParameter.GetEntry(FormatParameterDefinitionKeys.FormatStringEntryKey) as FieldFormattingDirective; } if (directive is null) @@ -483,7 +486,7 @@ private TableRowEntry GenerateTableRowEntryFromFromProperties(PSObject so, int e directive.isTable = true; } - fpf.propertyValue = this.GetExpressionDisplayValue(so, enumerationLimit, this.activeAssociationList[k].ResolvedExpression, directive); + fpf.propertyValue = this.GetExpressionDisplayValue(so, enumerationLimit, _activeAssociationList[k].ResolvedExpression, directive); tre.formatPropertyFieldList.Add(fpf); } diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Wide.cs b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Wide.cs index 26c0af670c7..bfa364cc450 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Wide.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormatViewGenerator_Wide.cs @@ -13,7 +13,40 @@ internal override void Initialize(TerminatingErrorContext errorContext, PSProper PSObject so, TypeInfoDataBase db, FormattingCommandLineParameters parameters) { base.Initialize(errorContext, expressionFactory, so, db, parameters); - this.inputParameters = parameters; + } + + /// <summary> + /// Builds the raw association list for wide formatting. + /// </summary> + protected override List<MshResolvedExpressionParameterAssociation> BuildRawAssociationList(PSObject so, List<MshParameter> propertyList) + { + // check if we received properties from the command line + if (propertyList is not null && propertyList.Count > 0) + { + return AssociationManager.ExpandParameters(propertyList, so); + } + + // we did not get any properties: + // try to get the display property of the object + PSPropertyExpression displayNameExpression = PSObjectHelper.GetDisplayNameExpression(so, this.expressionFactory); + if (displayNameExpression is not null) + { + return new List<MshResolvedExpressionParameterAssociation> + { + new MshResolvedExpressionParameterAssociation(null, displayNameExpression) + }; + } + + // try to get the default property set (we will use the first property) + var list = AssociationManager.ExpandDefaultPropertySet(so, this.expressionFactory); + if (list.Count == 0) + { + // we failed to get anything from the default property set + // just get all the properties + list = AssociationManager.ExpandAll(so); + } + + return list; } internal override FormatStartData GenerateStartData(PSObject so) @@ -130,20 +163,17 @@ private WideControlEntryDefinition GetActiveWideControlEntryDefinition(WideContr private WideViewEntry GenerateWideViewEntryFromProperties(PSObject so, int enumerationLimit) { - // compute active properties every time - if (this.activeAssociationList == null) - { - SetUpActiveProperty(so); - } + // Build active association list (with ExcludeProperty filter applied) + var associationList = BuildActiveAssociationList(so); WideViewEntry wve = new WideViewEntry(); FormatPropertyField fpf = new FormatPropertyField(); wve.formatPropertyField = fpf; - if (this.activeAssociationList.Count > 0) + if (associationList.Count > 0) { // get the first one - MshResolvedExpressionParameterAssociation a = this.activeAssociationList[0]; + MshResolvedExpressionParameterAssociation a = associationList[0]; FieldFormattingDirective directive = null; if (a.OriginatingParameter != null) { @@ -152,46 +182,7 @@ private WideViewEntry GenerateWideViewEntryFromProperties(PSObject so, int enume fpf.propertyValue = this.GetExpressionDisplayValue(so, enumerationLimit, a.ResolvedExpression, directive); } - - this.activeAssociationList = null; return wve; } - - private void SetUpActiveProperty(PSObject so) - { - List<MshParameter> rawMshParameterList = null; - - if (this.inputParameters != null) - rawMshParameterList = this.inputParameters.mshParameterList; - - // check if we received properties from the command line - if (rawMshParameterList != null && rawMshParameterList.Count > 0) - { - this.activeAssociationList = AssociationManager.ExpandParameters(rawMshParameterList, so); - return; - } - - // we did not get any properties: - // try to get the display property of the object - PSPropertyExpression displayNameExpression = PSObjectHelper.GetDisplayNameExpression(so, this.expressionFactory); - if (displayNameExpression != null) - { - this.activeAssociationList = new List<MshResolvedExpressionParameterAssociation>(); - this.activeAssociationList.Add(new MshResolvedExpressionParameterAssociation(null, displayNameExpression)); - return; - } - - // try to get the default property set (we will use the first property) - this.activeAssociationList = AssociationManager.ExpandDefaultPropertySet(so, this.expressionFactory); - if (this.activeAssociationList.Count > 0) - { - // we got a valid set of properties from the default property set - return; - } - - // we failed to get anything from the default property set - // just get all the properties - this.activeAssociationList = AssociationManager.ExpandAll(so); - } } } diff --git a/src/System.Management.Automation/FormatAndOutput/common/FormattingObjectsDeserializer.cs b/src/System.Management.Automation/FormatAndOutput/common/FormattingObjectsDeserializer.cs index 00923a103ec..7ae144702ba 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/FormattingObjectsDeserializer.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/FormattingObjectsDeserializer.cs @@ -54,7 +54,7 @@ fid is GroupEndData || return false; } - if (!(GetProperty(so, FormatInfoData.classidProperty) is string classId)) + if (GetProperty(so, FormatInfoData.classidProperty) is not string classId) { // it's not one of the objects derived from FormatInfoData return false; @@ -109,7 +109,7 @@ fid is GroupEndData || return so; } - if (!(GetProperty(so, FormatInfoData.classidProperty) is string classId)) + if (GetProperty(so, FormatInfoData.classidProperty) is not string classId) { // it's not one of the objects derived from FormatInfoData, // just return it as is diff --git a/src/System.Management.Automation/FormatAndOutput/common/ListWriter.cs b/src/System.Management.Automation/FormatAndOutput/common/ListWriter.cs index 7598c6b2797..988b1133748 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/ListWriter.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/ListWriter.cs @@ -259,7 +259,7 @@ private void WriteSingleLineHelper(string prependString, string line, LineOutput _cachedBuilder.Append(headPadding).Append(str); } - if (str.Contains(ValueStringDecorated.ESC) && !str.EndsWith(reset)) + if (str.Contains(ValueStringDecorated.ESC) && !str.AsSpan().TrimEnd().EndsWith(reset, StringComparison.Ordinal)) { _cachedBuilder.Append(reset); } diff --git a/src/System.Management.Automation/FormatAndOutput/common/PSStyle.cs b/src/System.Management.Automation/FormatAndOutput/common/PSStyle.cs index 3f927afd7d5..534f9541195 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/PSStyle.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/PSStyle.cs @@ -892,7 +892,7 @@ public static string MapColorPairToEscapeSequence(ConsoleColor foregroundColor, throw new ArgumentOutOfRangeException(paramName: nameof(foregroundColor)); } - if (backIndex < 0 || backIndex >= ForegroundColorMap.Length) + if (backIndex < 0 || backIndex >= BackgroundColorMap.Length) { throw new ArgumentOutOfRangeException(paramName: nameof(backgroundColor)); } diff --git a/src/System.Management.Automation/FormatAndOutput/common/Utilities/Mshexpression.cs b/src/System.Management.Automation/FormatAndOutput/common/Utilities/Mshexpression.cs index 552d511fd4e..55ee3c30ddb 100644 --- a/src/System.Management.Automation/FormatAndOutput/common/Utilities/Mshexpression.cs +++ b/src/System.Management.Automation/FormatAndOutput/common/Utilities/Mshexpression.cs @@ -375,5 +375,42 @@ private static PSObject IfHashtableWrapAsPSCustomObject(PSObject target, out boo private bool _isResolved = false; #endregion Private Members + + } + + /// <summary> + /// Helper class to do wildcard matching on PSPropertyExpressions. + /// </summary> + internal sealed class PSPropertyExpressionFilter + { + /// <summary> + /// Initializes a new instance of the <see cref="PSPropertyExpressionFilter"/> class + /// with the specified array of patterns. + /// </summary> + /// <param name="wildcardPatternsStrings">Array of pattern strings to use.</param> + internal PSPropertyExpressionFilter(string[] wildcardPatternsStrings) + { + ArgumentNullException.ThrowIfNull(wildcardPatternsStrings); + + _wildcardPatterns = new WildcardPattern[wildcardPatternsStrings.Length]; + for (int k = 0; k < wildcardPatternsStrings.Length; k++) + { + _wildcardPatterns[k] = WildcardPattern.Get(wildcardPatternsStrings[k], WildcardOptions.IgnoreCase); + } + } + + /// <summary> + /// Try to match the expression against the array of wildcard patterns. + /// The first match short-circuits the search. + /// </summary> + /// <param name="expression">PSPropertyExpression to test against.</param> + /// <returns>True if there is a match, else false.</returns> + internal bool IsMatch(PSPropertyExpression expression) + { + string expressionString = expression.ToString(); + return _wildcardPatterns.Any(pattern => pattern.IsMatch(expressionString)); + } + + private readonly WildcardPattern[] _wildcardPatterns; } } diff --git a/src/System.Management.Automation/SourceGenerators/PSVersionInfoGenerator/PSVersionInfoGenerator.csproj b/src/System.Management.Automation/SourceGenerators/PSVersionInfoGenerator/PSVersionInfoGenerator.csproj index 1e53a35f966..6ba483b5f6e 100644 --- a/src/System.Management.Automation/SourceGenerators/PSVersionInfoGenerator/PSVersionInfoGenerator.csproj +++ b/src/System.Management.Automation/SourceGenerators/PSVersionInfoGenerator/PSVersionInfoGenerator.csproj @@ -7,14 +7,14 @@ <PropertyGroup> <!-- source generator project needs to target 'netstandard2.0' --> <TargetFramework>netstandard2.0</TargetFramework> - <LangVersion>13.0</LangVersion> + <LangVersion>preview</LangVersion> <SuppressNETCoreSdkPreviewMessage>true</SuppressNETCoreSdkPreviewMessage> <EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules> <Nullable>enable</Nullable> </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" PrivateAssets="all" /> - <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="4.14.0" PrivateAssets="all" /> + <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="5.3.0" PrivateAssets="all" /> + <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="5.3.0" PrivateAssets="all" /> </ItemGroup> </Project> diff --git a/src/System.Management.Automation/System.Management.Automation.csproj b/src/System.Management.Automation/System.Management.Automation.csproj index 15465ab323f..f3e1d0dd9e8 100644 --- a/src/System.Management.Automation/System.Management.Automation.csproj +++ b/src/System.Management.Automation/System.Management.Automation.csproj @@ -28,28 +28,21 @@ <ItemGroup> <!-- the following package(s) are from https://github.com/JamesNK/Newtonsoft.Json --> - <PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> + <PackageReference Include="Newtonsoft.Json" Version="13.0.4" /> <!-- the Application Insights package --> <PackageReference Include="Microsoft.ApplicationInsights" Version="2.23.0" /> <!-- the following package(s) are from https://github.com/dotnet/corefx --> - <PackageReference Include="Microsoft.Win32.Registry.AccessControl" Version="10.0.0-preview.6.25358.103" /> - <PackageReference Include="System.Configuration.ConfigurationManager" Version="10.0.0-preview.6.25358.103" /> - <!-- Removing due to NU1510 --> - <!-- PackageReference Include="System.Diagnostics.DiagnosticSource" Version="9.0.2" /--> - <PackageReference Include="System.DirectoryServices" Version="10.0.0-preview.6.25358.103" /> - <!--PackageReference Include="System.IO.FileSystem.AccessControl" Version="6.0.0-preview.5.21301.5" /--> - <PackageReference Include="System.Management" Version="10.0.0-preview.6.25358.103" /> - <!-- Removing System.Security.AccessControl as per NU1510 --> - <!-- PackageReference Include="System.Security.AccessControl" Version="6.0.1" /--> - <PackageReference Include="System.Security.Cryptography.Pkcs" Version="10.0.0-preview.6.25358.103" /> - <PackageReference Include="System.Security.Permissions" Version="10.0.0-preview.6.25358.103" /> - <!-- Removing due to NU1510 --> - <!-- PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.2" /--> + <PackageReference Include="Microsoft.Win32.Registry.AccessControl" Version="11.0.0-preview.3.26207.106" /> + <PackageReference Include="System.Configuration.ConfigurationManager" Version="11.0.0-preview.3.26207.106" /> + <PackageReference Include="System.DirectoryServices" Version="11.0.0-preview.3.26207.106" /> + <PackageReference Include="System.Management" Version="11.0.0-preview.3.26207.106" /> + <PackageReference Include="System.Security.Cryptography.Pkcs" Version="11.0.0-preview.3.26207.106" /> + <PackageReference Include="System.Security.Permissions" Version="11.0.0-preview.3.26207.106" /> <!-- the following package(s) are from the powershell org --> <PackageReference Include="Microsoft.Management.Infrastructure" Version="3.0.0" /> - <PackageReference Include="Microsoft.PowerShell.Native" Version="7.4.0" /> + <PackageReference Include="Microsoft.PowerShell.Native" Version="700.0.0" /> <!-- Signing APIs --> - <PackageReference Include="Microsoft.Security.Extensions" Version="1.4.0" /> + <PackageReference Include="Microsoft.Security.Extensions" Version="1.4.0" /> </ItemGroup> <PropertyGroup> diff --git a/src/System.Management.Automation/cimSupport/cmdletization/QueryBuilder.cs b/src/System.Management.Automation/cimSupport/cmdletization/QueryBuilder.cs index 36f781d98a0..e71e4c7c04f 100644 --- a/src/System.Management.Automation/cimSupport/cmdletization/QueryBuilder.cs +++ b/src/System.Management.Automation/cimSupport/cmdletization/QueryBuilder.cs @@ -52,7 +52,7 @@ public abstract class QueryBuilder /// <param name="propertyName">Property name to query on.</param> /// <param name="allowedPropertyValues">Property values to accept in the query.</param> /// <param name="wildcardsEnabled"> - /// <see langword="true"/> if <paramref name="allowedPropertyValues"/> should be treated as a <see cref="System.String"/> containing a wildcard pattern; + /// <see langword="true"/> if <paramref name="allowedPropertyValues"/> should be treated as a <see cref="string"/> containing a wildcard pattern; /// <see langword="false"/> otherwise. /// </param> /// <param name="behaviorOnNoMatch"> @@ -69,7 +69,7 @@ public virtual void FilterByProperty(string propertyName, IEnumerable allowedPro /// <param name="propertyName">Property name to query on.</param> /// <param name="excludedPropertyValues">Property values to reject in the query.</param> /// <param name="wildcardsEnabled"> - /// <see langword="true"/> if <paramref name="excludedPropertyValues"/> should be treated as a <see cref="System.String"/> containing a wildcard pattern; + /// <see langword="true"/> if <paramref name="excludedPropertyValues"/> should be treated as a <see cref="string"/> containing a wildcard pattern; /// <see langword="false"/> otherwise. /// </param> /// <param name="behaviorOnNoMatch"> diff --git a/src/System.Management.Automation/cimSupport/cmdletization/xml/cmdlets-over-objects.objectModel.autogen.cs b/src/System.Management.Automation/cimSupport/cmdletization/xml/cmdlets-over-objects.objectModel.autogen.cs index e2b87d4adf9..7274c3bb954 100644 --- a/src/System.Management.Automation/cimSupport/cmdletization/xml/cmdlets-over-objects.objectModel.autogen.cs +++ b/src/System.Management.Automation/cimSupport/cmdletization/xml/cmdlets-over-objects.objectModel.autogen.cs @@ -2237,4 +2237,3 @@ public string Value } } } - diff --git a/src/System.Management.Automation/cimSupport/cmdletization/xml/cmdlets-over-objects.xmlSerializer.autogen.cs b/src/System.Management.Automation/cimSupport/cmdletization/xml/cmdlets-over-objects.xmlSerializer.autogen.cs index f463e5052bb..dd1c6023cdb 100644 --- a/src/System.Management.Automation/cimSupport/cmdletization/xml/cmdlets-over-objects.xmlSerializer.autogen.cs +++ b/src/System.Management.Automation/cimSupport/cmdletization/xml/cmdlets-over-objects.xmlSerializer.autogen.cs @@ -760,7 +760,7 @@ private void Write12_Item(string n, string ns, global::Microsoft.PowerShell.Cmdl { WriteXsiType(@"CmdletParameterMetadataForGetCmdletFilteringParameter", @"http://schemas.microsoft.com/cmdlets-over-objects/2009/11"); } - + if (o.@IsMandatorySpecified) { WriteAttribute(@"IsMandatory", @"", System.Xml.XmlConvert.ToString((global::System.Boolean)((global::System.Boolean)o.@IsMandatory))); @@ -10108,4 +10108,3 @@ public override System.Xml.Serialization.XmlSerializer GetSerializer(System.Type } } } - diff --git a/src/System.Management.Automation/cimSupport/other/ciminstancetypeadapter.cs b/src/System.Management.Automation/cimSupport/other/ciminstancetypeadapter.cs index 9a3e5f137ad..202c3e98e48 100644 --- a/src/System.Management.Automation/cimSupport/other/ciminstancetypeadapter.cs +++ b/src/System.Management.Automation/cimSupport/other/ciminstancetypeadapter.cs @@ -267,7 +267,7 @@ private static List<CimClass> GetInheritanceChain(CimInstance cimInstance) /// <returns></returns> public override Collection<string> GetTypeNameHierarchy(object baseObject) { - if (!(baseObject is CimInstance cimInstance)) + if (baseObject is not CimInstance cimInstance) { throw new ArgumentNullException(nameof(baseObject)); } @@ -343,7 +343,7 @@ public override bool IsSettable(PSAdaptedProperty adaptedProperty) return false; } - if (!(adaptedProperty.Tag is CimProperty cimProperty)) + if (adaptedProperty.Tag is not CimProperty cimProperty) { return false; } diff --git a/src/System.Management.Automation/engine/ArgumentTypeConverterAttribute.cs b/src/System.Management.Automation/engine/ArgumentTypeConverterAttribute.cs index a2c2cd8321e..596a4142e93 100644 --- a/src/System.Management.Automation/engine/ArgumentTypeConverterAttribute.cs +++ b/src/System.Management.Automation/engine/ArgumentTypeConverterAttribute.cs @@ -65,7 +65,7 @@ internal object Transform(EngineIntrinsics engineIntrinsics, object inputData, b else temp = result; - if (!(temp is PSReference reference)) + if (temp is not PSReference reference) { throw new PSInvalidCastException("InvalidCastExceptionReferenceTypeExpected", null, ExtendedTypeSystem.ReferenceTypeExpected); diff --git a/src/System.Management.Automation/engine/Attributes.cs b/src/System.Management.Automation/engine/Attributes.cs index 2f7de2a2149..dac3a2ac377 100644 --- a/src/System.Management.Automation/engine/Attributes.cs +++ b/src/System.Management.Automation/engine/Attributes.cs @@ -87,7 +87,7 @@ namespace System.Management.Automation /// <see cref="ValidateArgumentsAttribute"/> validates the argument as a whole. If the argument value may /// be an enumerable, you can derive from <see cref="ValidateEnumeratedArgumentsAttribute"/> /// which will take care of unrolling the enumerable and validate each element individually. - /// It is also recommended to override <see cref="System.Object.ToString"/> to return a readable string + /// It is also recommended to override <see cref="object.ToString"/> to return a readable string /// similar to the attribute declaration, for example "[ValidateRangeAttribute(5,10)]". /// If this attribute is applied to a string parameter, the string command argument will be validated. /// If this attribute is applied to a string[] parameter, the string[] command argument will be validated. @@ -154,7 +154,7 @@ protected ValidateArgumentsAttribute() /// <seealso cref="ValidateEnumeratedArgumentsAttribute"/> and override the /// <seealso cref="ValidateEnumeratedArgumentsAttribute.ValidateElement"/> /// abstract method, after which they can apply the attribute to their parameters. - /// It is also recommended to override <see cref="System.Object.ToString"/> to return a readable string + /// It is also recommended to override <see cref="object.ToString"/> to return a readable string /// similar to the attribute declaration, for example "[ValidateRangeAttribute(5,10)]". /// If this attribute is applied to a string parameter, the string command argument will be validated. /// If this attribute is applied to a string[] parameter, each string command argument will be validated. @@ -844,7 +844,7 @@ public sealed class ValidateLengthAttribute : ValidateEnumeratedArgumentsAttribu /// <exception cref="ArgumentException">For invalid arguments.</exception> protected override void ValidateElement(object element) { - if (!(element is string objectString)) + if (element is not string objectString) { throw new ValidationMetadataException( "ValidateLengthNotString", @@ -1956,7 +1956,7 @@ protected override void Validate(object arguments, EngineIntrinsics engineIntrin Metadata.ValidateNotNullFailure); } - if (!(arguments is string path)) + if (arguments is not string path) { throw new ValidationMetadataException( "PathArgumentIsNotValid", @@ -2292,7 +2292,7 @@ public ValidateNotNullOrWhiteSpaceAttribute() /// <see cref="ArgumentTransformationAttribute"/> and override the /// <see cref="ArgumentTransformationAttribute.Transform"/> abstract method, after which they /// can apply the attribute to their parameters. - /// It is also recommended to override <see cref="System.Object.ToString"/> to return a readable + /// It is also recommended to override <see cref="object.ToString"/> to return a readable /// string similar to the attribute declaration, for example "[ValidateRangeAttribute(5,10)]". /// If multiple transformations are defined on a parameter, they will be invoked in series, /// each getting the output of the previous transformation. diff --git a/src/System.Management.Automation/engine/COM/ComInvoker.cs b/src/System.Management.Automation/engine/COM/ComInvoker.cs index a1b4a4c1ba7..ea8b8b96d79 100644 --- a/src/System.Management.Automation/engine/COM/ComInvoker.cs +++ b/src/System.Management.Automation/engine/COM/ComInvoker.cs @@ -7,9 +7,6 @@ using COM = System.Runtime.InteropServices.ComTypes; -// Disable obsolete warnings about VarEnum and COM-marshaling APIs in CoreCLR -#pragma warning disable 618 - namespace System.Management.Automation { internal static class ComInvoker diff --git a/src/System.Management.Automation/engine/COM/ComUtil.cs b/src/System.Management.Automation/engine/COM/ComUtil.cs index e244e4cdf3f..8cd5e73b835 100644 --- a/src/System.Management.Automation/engine/COM/ComUtil.cs +++ b/src/System.Management.Automation/engine/COM/ComUtil.cs @@ -141,9 +141,6 @@ private static string GetStringFromCustomType(COM.ITypeInfo typeinfo, IntPtr ref return "UnknownCustomtype"; } - // Disable obsolete warning about VarEnum in CoreCLR -#pragma warning disable 618 - /// <summary> /// This function gets a string representation of the Type Descriptor /// This is used in generating signature for Properties and Methods. @@ -259,8 +256,6 @@ internal static Type GetTypeFromTypeDesc(COM.TYPEDESC typedesc) return VarEnumSelector.GetTypeForVarEnum(vt); } -#pragma warning restore 618 - /// <summary> /// Converts a FuncDesc out of GetFuncDesc into a MethodInformation. /// </summary> diff --git a/src/System.Management.Automation/engine/CmdletParameterBinderController.cs b/src/System.Management.Automation/engine/CmdletParameterBinderController.cs index 88fadc3342d..b9cf67b29e6 100644 --- a/src/System.Management.Automation/engine/CmdletParameterBinderController.cs +++ b/src/System.Management.Automation/engine/CmdletParameterBinderController.cs @@ -622,7 +622,7 @@ private Dictionary<MergedCompiledCommandParameter, object> GetDefaultParameterVa foreach (DictionaryEntry entry in DefaultParameterValues) { - if (!(entry.Key is string key)) + if (entry.Key is not string key) { continue; } @@ -4386,7 +4386,7 @@ public override bool ContainsKey(object key) throw PSTraceSource.NewArgumentNullException(nameof(key)); } - if (!(key is string strKey)) + if (key is not string strKey) { return false; } @@ -4415,7 +4415,7 @@ private void AddImpl(object key, object value, bool isSelfIndexing) throw PSTraceSource.NewArgumentNullException(nameof(key)); } - if (!(key is string strKey)) + if (key is not string strKey) { throw PSTraceSource.NewArgumentException(nameof(key), ParameterBinderStrings.StringValueKeyExpected, key, key.GetType().FullName); } @@ -4463,7 +4463,7 @@ public override object this[object key] throw PSTraceSource.NewArgumentNullException(nameof(key)); } - if (!(key is string strKey)) + if (key is not string strKey) { return null; } @@ -4489,7 +4489,7 @@ public override void Remove(object key) throw PSTraceSource.NewArgumentNullException(nameof(key)); } - if (!(key is string strKey)) + if (key is not string strKey) { return; } diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs index 42a60ab7e0e..9c4a61a6833 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs @@ -251,8 +251,7 @@ private static bool CompleteAgainstSwitchFile(Ast lastAst, Token tokenBeforeCurs { Tuple<Token, Ast> fileConditionTuple; - var errorStatement = lastAst as ErrorStatementAst; - if (errorStatement != null && errorStatement.Flags != null && errorStatement.Kind != null && tokenBeforeCursor != null && + if (lastAst is ErrorStatementAst errorStatement && errorStatement.Flags is not null && errorStatement.Kind is not null && tokenBeforeCursor is not null && errorStatement.Kind.Kind.Equals(TokenKind.Switch) && errorStatement.Flags.TryGetValue("file", out fileConditionTuple)) { // Handle "switch -file <tab>" @@ -262,24 +261,239 @@ private static bool CompleteAgainstSwitchFile(Ast lastAst, Token tokenBeforeCurs if (lastAst.Parent is CommandExpressionAst) { // Handle "switch -file m<tab>" or "switch -file *.ps1<tab>" - if (!(lastAst.Parent.Parent is PipelineAst pipeline)) + if (lastAst.Parent.Parent is not PipelineAst pipeline) { return false; } - errorStatement = pipeline.Parent as ErrorStatementAst; - if (errorStatement == null || errorStatement.Kind == null || errorStatement.Flags == null) + if (pipeline.Parent is not ErrorStatementAst parentErrorStatement || parentErrorStatement.Kind is null || parentErrorStatement.Flags is null) { return false; } - return (errorStatement.Kind.Kind.Equals(TokenKind.Switch) && - errorStatement.Flags.TryGetValue("file", out fileConditionTuple) && fileConditionTuple.Item2 == pipeline); + return (parentErrorStatement.Kind.Kind.Equals(TokenKind.Switch) && + parentErrorStatement.Flags.TryGetValue("file", out fileConditionTuple) && fileConditionTuple.Item2 == pipeline); } return false; } + /// <summary> + /// Check if we should complete parameter names for switch cases on $PSBoundParameters.Keys + /// </summary> + private static List<CompletionResult> CompleteAgainstSwitchCaseCondition(CompletionContext completionContext) + { + var lastAst = completionContext.RelatedAsts.Last(); + + PipelineAst conditionPipeline = null; + Ast switchAst = null; + + // Check if we're in a switch statement (complete) or error statement (incomplete switch) + if (lastAst.Parent is SwitchStatementAst switchStatementAst) + { + // Verify that the lastAst is one of the clause conditions (not in the body) + bool isClauseCondition = switchStatementAst.Clauses.Any(clause => clause.Item1 == lastAst); + + if (!isClauseCondition) + { + return null; + } + + conditionPipeline = switchStatementAst.Condition as PipelineAst; + switchAst = switchStatementAst; + } + else + { + // Check for incomplete switch parsed as ErrorStatementAst + if (lastAst.Parent is not ErrorStatementAst errorStatementAst || errorStatementAst.Kind is null || + errorStatementAst.Kind.Kind != TokenKind.Switch) + { + return null; + } + + // For ErrorStatementAst, the case value is in Bodies, condition is in Conditions + bool isInBodies = errorStatementAst.Bodies != null && errorStatementAst.Bodies.Any(body => body == lastAst); + + if (!isInBodies) + { + return null; + } + + // Get the condition from ErrorStatementAst.Conditions + if (errorStatementAst.Conditions != null && errorStatementAst.Conditions.Count > 0) + { + conditionPipeline = errorStatementAst.Conditions[0] as PipelineAst; + } + switchAst = errorStatementAst; + } + + if (conditionPipeline == null || conditionPipeline.PipelineElements.Count != 1) + { + return null; + } + + if (conditionPipeline.PipelineElements[0] is not CommandExpressionAst commandExpressionAst) + { + return null; + } + + // Check if the expression is a member access on $PSBoundParameters.Keys + if (commandExpressionAst.Expression is not MemberExpressionAst memberExpressionAst) + { + return null; + } + + // Check if the target is $PSBoundParameters + if (memberExpressionAst.Expression is not VariableExpressionAst variableExpressionAst || + !variableExpressionAst.VariablePath.UserPath.Equals("PSBoundParameters", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + // Check if the member is "Keys" + if (memberExpressionAst.Member is not StringConstantExpressionAst memberNameAst || + !memberNameAst.Value.Equals("Keys", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + // Find the nearest param block by traversing up the AST + var paramBlockAst = FindNearestParamBlock(switchAst.Parent); + + if (paramBlockAst == null || paramBlockAst.Parameters.Count == 0) + { + return null; + } + + // Generate completion results from parameter names + var wordToComplete = completionContext.WordToComplete ?? string.Empty; + return CreateParameterCompletionResults(paramBlockAst, wordToComplete); + } + + /// <summary> + /// Check if we should complete parameter names for $PSBoundParameters access patterns + /// Supports: $PSBoundParameters.ContainsKey('...'), $PSBoundParameters['...'], $PSBoundParameters.Remove('...') + /// </summary> + private static List<CompletionResult> CompleteAgainstPSBoundParametersAccess(CompletionContext completionContext) + { + var lastAst = completionContext.RelatedAsts.Last(); + + // Must be a string constant + if (lastAst is not StringConstantExpressionAst stringAst) + { + return null; + } + + ExpressionAst targetAst = null; + + // Check for method invocation: $PSBoundParameters.ContainsKey('...') or $PSBoundParameters.Remove('...') + if (lastAst.Parent is InvokeMemberExpressionAst invokeMemberAst) + { + if (invokeMemberAst.Member is StringConstantExpressionAst memberName && + (memberName.Value.Equals("ContainsKey", StringComparison.OrdinalIgnoreCase) || + memberName.Value.Equals("Remove", StringComparison.OrdinalIgnoreCase))) + { + targetAst = invokeMemberAst.Expression; + } + } + // Check for indexer: $PSBoundParameters['...'] + else if (lastAst.Parent is IndexExpressionAst indexAst) + { + targetAst = indexAst.Target; + } + + if (targetAst is null) + { + return null; + } + + // Check if target is $PSBoundParameters + if (targetAst is not VariableExpressionAst variableAst || + !variableAst.VariablePath.UserPath.Equals("PSBoundParameters", StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + // Find the nearest param block + var paramBlockAst = FindNearestParamBlock(lastAst.Parent); + + if (paramBlockAst == null || paramBlockAst.Parameters.Count == 0) + { + return null; + } + + // Generate completion results from parameter names + var wordToComplete = completionContext.WordToComplete ?? string.Empty; + + // Determine quote style based on the string constant type + string quoteChar = string.Empty; + if (stringAst.StringConstantType == StringConstantType.SingleQuoted) + { + quoteChar = "'"; + } + else if (stringAst.StringConstantType == StringConstantType.DoubleQuoted) + { + quoteChar = "\""; + } + + return CreateParameterCompletionResults(paramBlockAst, wordToComplete, quoteChar); + } + + /// <summary> + /// Finds the nearest ParamBlockAst by traversing up the AST hierarchy. + /// </summary> + /// <param name="startAst">The AST node to start searching from.</param> + /// <returns>The nearest ParamBlockAst if found; otherwise, null.</returns> + private static ParamBlockAst FindNearestParamBlock(Ast startAst) + { + Ast current = startAst; + while (current != null) + { + if (current is FunctionDefinitionAst functionDefinitionAst) + { + return functionDefinitionAst.Body?.ParamBlock; + } + else if (current is ScriptBlockAst scriptBlockAst) + { + var paramBlock = scriptBlockAst.ParamBlock; + if (paramBlock != null) + { + return paramBlock; + } + } + + current = current.Parent; + } + + return null; + } + + /// <summary> + /// Creates completion results from parameter names with optional quote wrapping. + /// </summary> + /// <param name="paramBlockAst">The parameter block containing parameters to complete.</param> + /// <param name="wordToComplete">The partial word to match against parameter names.</param> + /// <param name="quoteChar">Optional quote character to wrap completion text (empty string for no quotes).</param> + /// <returns>A list of completion results, or null if no matches found.</returns> + private static List<CompletionResult> CreateParameterCompletionResults( + ParamBlockAst paramBlockAst, + string wordToComplete, + string quoteChar = "") + { + var result = paramBlockAst.Parameters + .Select(parameter => parameter.Name.VariablePath.UserPath) + .Where(parameterName => parameterName.StartsWith(wordToComplete, StringComparison.OrdinalIgnoreCase)) + .Select(parameterName => + new CompletionResult( + quoteChar + parameterName + quoteChar, + parameterName, + CompletionResultType.ParameterValue, + parameterName)) + .ToList(); + + return result.Count > 0 ? result : null; + } + private static bool CompleteOperator(Token tokenAtCursor, Ast lastAst) { if (tokenAtCursor.Kind == TokenKind.Minus) @@ -387,7 +601,10 @@ internal List<CompletionResult> GetResults(PowerShell powerShell, out int replac completionContext.ExecutionContext.LanguageMode = PSLanguageMode.ConstrainedLanguage; } - return GetResultHelper(completionContext, out replacementIndex, out replacementLength); + List<CompletionResult> results = GetResultHelper(completionContext, out replacementIndex, out replacementLength); + CompletionCompleters.RemoveLastNullCompletionResult(results); + + return results; } finally { @@ -516,6 +733,13 @@ internal List<CompletionResult> GetResultHelper(CompletionContext completionCont { // Handles quoted string inside index expression like: $PSVersionTable["<Tab>"] completionContext.WordToComplete = (tokenAtCursor as StringToken).Value; + // Check for $PSBoundParameters indexer first + var psBoundResult = CompleteAgainstPSBoundParametersAccess(completionContext); + if (psBoundResult != null && psBoundResult.Count > 0) + { + return psBoundResult; + } + return CompletionCompleters.CompleteIndexExpression(completionContext, indexExpressionAst.Target); } @@ -1503,7 +1727,7 @@ private static bool TryGetInferredCompletionsForAssignment(Ast expression, Compl { return false; } - + if (inferredTypes.Count == 0) { return false; @@ -1915,6 +2139,21 @@ private static List<CompletionResult> GetResultForString(CompletionContext compl string strValue = constantString != null ? constantString.Value : expandableString.Value; + // Check for switch case completion on $PSBoundParameters.Keys + completionContext.WordToComplete = strValue; + var switchCaseResult = CompleteAgainstSwitchCaseCondition(completionContext); + if (switchCaseResult != null && switchCaseResult.Count > 0) + { + return switchCaseResult; + } + + // Check for $PSBoundParameters access patterns (ContainsKey, indexer, Remove) + var psBoundResult = CompleteAgainstPSBoundParametersAccess(completionContext); + if (psBoundResult != null && psBoundResult.Count > 0) + { + return psBoundResult; + } + bool shouldContinue; List<CompletionResult> result = GetResultForEnumPropertyValueOfDSCResource(completionContext, strValue, ref replacementIndex, ref replacementLength, out shouldContinue); if (!shouldContinue || (result != null && result.Count > 0)) @@ -2076,6 +2315,20 @@ private static List<CompletionResult> GetResultForIdentifier(CompletionContext c var tokenAtCursorText = tokenAtCursor.Text; completionContext.WordToComplete = tokenAtCursorText; + // Check for switch case completion on $PSBoundParameters.Keys + var switchCaseResult = CompleteAgainstSwitchCaseCondition(completionContext); + if (switchCaseResult != null && switchCaseResult.Count > 0) + { + return switchCaseResult; + } + + // Check for $PSBoundParameters access patterns (ContainsKey, indexer, Remove) + var psBoundResult = CompleteAgainstPSBoundParametersAccess(completionContext); + if (psBoundResult != null && psBoundResult.Count > 0) + { + return psBoundResult; + } + if (lastAst.Parent is BreakStatementAst || lastAst.Parent is ContinueStatementAst) { return CompleteLoopLabel(completionContext); @@ -2613,7 +2866,7 @@ private static List<CompletionResult> CompleteLoopLabel(CompletionContext comple return result; } - + private static List<CompletionResult> CompleteUsingKeywords(int cursorOffset, Token[] tokens, ref int replacementIndex, ref int replacementLength) { var result = new List<CompletionResult>(); diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs index 880aedb04ce..80cead788d3 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs @@ -64,7 +64,6 @@ public static IEnumerable<CompletionResult> CompleteCommand(string commandName) /// <param name="moduleName"></param> /// <param name="commandTypes"></param> /// <returns></returns> - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] public static IEnumerable<CompletionResult> CompleteCommand(string commandName, string moduleName, CommandTypes commandTypes = CommandTypes.All) { var runspace = Runspace.DefaultRunspace; @@ -449,12 +448,12 @@ internal static List<CompletionResult> CompleteModuleName(CompletionContext cont // eg: Host<Tab> finds Microsoft.PowerShell.Host // If the user has entered a manual wildcard, or a module name that contains a "." we assume they only want results that matches the input exactly. bool shortNameSearch = wordToComplete.Length > 0 && !WildcardPattern.ContainsWildcardCharacters(wordToComplete) && !wordToComplete.Contains('.'); - + if (!wordToComplete.EndsWith('*')) { wordToComplete += "*"; } - + string[] moduleNames; WildcardPattern shortNamePattern; if (shortNameSearch) @@ -603,7 +602,7 @@ internal static List<CompletionResult> CompleteCommandParameter(CompletionContex else { // No CommandParameterAst is found. It could be a StringConstantExpressionAst "-" - if (!(context.RelatedAsts[context.RelatedAsts.Count - 1] is StringConstantExpressionAst dashAst)) + if (context.RelatedAsts[context.RelatedAsts.Count - 1] is not StringConstantExpressionAst dashAst) return result; if (!dashAst.Value.Trim().Equals("-", StringComparison.OrdinalIgnoreCase)) return result; @@ -849,7 +848,7 @@ private static bool TryGetParameterHelpMessage( [NotNullWhen(true)] out string? message) { message = null; - + if (attr.HelpMessage is not null) { message = attr.HelpMessage; @@ -927,7 +926,7 @@ private static List<CompletionResult> GetParameterCompletionResults( addCommonParameters = false; break; } - + if (helpMessage is null && TryGetParameterHelpMessage(pattr, commandAssembly, out string attrHelpMessage)) { helpMessage = $" - {attrHelpMessage}"; @@ -2505,7 +2504,8 @@ private static void NativeCommandArgumentCompletion( case "Format-Table": case "Format-Wide": { - if (parameterName.Equals("Property", StringComparison.OrdinalIgnoreCase)) + if (parameterName.Equals("Property", StringComparison.OrdinalIgnoreCase) + || parameterName.Equals("ExcludeProperty", StringComparison.OrdinalIgnoreCase)) { NativeCompletionMemberName(context, result, commandAst, boundArguments?[parameterName]); } @@ -2638,9 +2638,8 @@ private static ScriptBlock GetCustomArgumentCompleter( } } - var registeredCompleters = optionKey.Equals("NativeArgumentCompleters", StringComparison.OrdinalIgnoreCase) - ? context.NativeArgumentCompleters - : context.CustomArgumentCompleters; + bool isNative = optionKey.Equals("NativeArgumentCompleters", StringComparison.OrdinalIgnoreCase); + var registeredCompleters = isNative ? context.NativeArgumentCompleters : context.CustomArgumentCompleters; if (registeredCompleters != null) { @@ -2651,6 +2650,13 @@ private static ScriptBlock GetCustomArgumentCompleter( return scriptBlock; } } + + // For a native command, if a fallback completer is registered, then return it. + // For example, the 'Microsoft.PowerShell.UnixTabCompletion' module. + if (isNative && registeredCompleters.TryGetValue(RegisterArgumentCompleterCommand.FallbackCompleterKey, out scriptBlock)) + { + return scriptBlock; + } } return null; @@ -2669,14 +2675,17 @@ private static bool InvokeScriptArgumentCompleter( scriptBlock, new object[] { commandName, parameterName, wordToComplete, commandAst, GetBoundArgumentsAsHashtable(context) }, resultList); - if (result) - { - resultList.Add(CompletionResult.Null); - } return result; } + /// <summary> + /// Invoke the custom argument completer and process its return values. + /// If we consider the completion successful, we add a null instance of the type 'CompletionResult' + /// to the end of the 'result' list to indicate that the argument completion has been processed, so we + /// will not go through the default argument completion even if the 'result' list is still empty. + /// </summary> + /// <returns>'true' if the argument completion was successful. 'false' otherwise.</returns> private static bool InvokeScriptArgumentCompleter( ScriptBlock scriptBlock, object[] argumentsToCompleter, @@ -2696,20 +2705,44 @@ private static bool InvokeScriptArgumentCompleter( return false; } + if (customResults.Count is 1 && customResults[0] is { BaseObject: "" } or null) + { + // If the script block returns a single empty string or a null value, we will treat it as if it has + // completed successfully but has no results to return. + // This allows a custom completer to suppress the default completions that we may fall back otherwise. + result.Add(CompletionResult.Null); + return true; + } + + int initialCount = result.Count; + foreach (var customResult in customResults) { - var resultAsCompletion = customResult.BaseObject as CompletionResult; - if (resultAsCompletion != null) + if (customResult is null) + { + continue; + } + + if (customResult.BaseObject is CompletionResult resultAsCompletion) { result.Add(resultAsCompletion); continue; } var resultAsString = customResult.ToString(); - result.Add(new CompletionResult(resultAsString)); + if (!string.IsNullOrEmpty(resultAsString)) + { + result.Add(new CompletionResult(resultAsString)); + } } - return true; + bool success = result.Count > initialCount; + if (success) + { + result.Add(CompletionResult.Null); + } + + return success; } // All the methods for native command argument completion will add a null instance of the type CompletionResult to the end of the @@ -2717,9 +2750,9 @@ private static bool InvokeScriptArgumentCompleter( // and has been processed already. So if the "result" list is still empty afterward, we will not go through the default argument completion anymore. #region Native Command Argument Completion - private static void RemoveLastNullCompletionResult(List<CompletionResult> result) + internal static void RemoveLastNullCompletionResult(List<CompletionResult> result) { - if (result.Count > 0 && result[result.Count - 1].Equals(CompletionResult.Null)) + if (result?.Count > 0 && result[^1].Equals(CompletionResult.Null)) { result.RemoveAt(result.Count - 1); } @@ -3125,7 +3158,7 @@ private static void NativeCompletionCimNamespace( continue; } - if (!(namespaceNameProperty.Value is string childNamespace)) + if (namespaceNameProperty.Value is not string childNamespace) { continue; } @@ -4575,8 +4608,8 @@ internal static IEnumerable<CompletionResult> CompleteFilename(CompletionContext return CommandCompletion.EmptyCompletionResult; } - var lastAst = context.RelatedAsts[^1]; - if (lastAst.Parent is UsingStatementAst usingStatement + var lastAst = context.RelatedAsts?[^1]; + if (lastAst?.Parent is UsingStatementAst usingStatement && usingStatement.UsingStatementKind is UsingStatementKind.Module or UsingStatementKind.Assembly && lastAst.Extent.File is not null) { @@ -4767,7 +4800,7 @@ private static List<CompletionResult> GetFileSystemProviderResults( var resultType = isContainer ? CompletionResultType.ProviderContainer : CompletionResultType.ProviderItem; - + bool leafQuotesNeeded; var completionText = NewPathCompletionText( basePath, @@ -4991,7 +5024,7 @@ private static string RebuildPathWithVars( for (int i = 0; i < path.Length; i++) { // on Windows, we need to preserve the expanded home path as native commands don't understand it -#if UNIX +#if UNIX if (i == homeIndex) { _ = sb.Append('~'); @@ -5199,38 +5232,48 @@ internal static List<string> GetFileShares(string machine, bool ignoreHidden) uint numEntries = 0; uint totalEntries; uint resumeHandle = 0; - int result = Interop.Windows.NetShareEnum( - machine, - level: 1, - out shBuf, - Interop.Windows.MAX_PREFERRED_LENGTH, - out numEntries, - out totalEntries, - ref resumeHandle); - - var shares = new List<string>(); - if (result == Interop.Windows.ERROR_SUCCESS || result == Interop.Windows.ERROR_MORE_DATA) + try { - for (int i = 0; i < numEntries; ++i) - { - nint curInfoPtr = shBuf + (Marshal.SizeOf<SHARE_INFO_1>() * i); - SHARE_INFO_1 shareInfo = Marshal.PtrToStructure<SHARE_INFO_1>(curInfoPtr); + int result = Interop.Windows.NetShareEnum( + machine, + level: 1, + out shBuf, + Interop.Windows.MAX_PREFERRED_LENGTH, + out numEntries, + out totalEntries, + ref resumeHandle); - if ((shareInfo.type & Interop.Windows.STYPE_MASK) != Interop.Windows.STYPE_DISKTREE) + var shares = new List<string>(); + if (result == Interop.Windows.ERROR_SUCCESS || result == Interop.Windows.ERROR_MORE_DATA) + { + for (int i = 0; i < numEntries; ++i) { - continue; - } + nint curInfoPtr = shBuf + (Marshal.SizeOf<SHARE_INFO_1>() * i); + SHARE_INFO_1 shareInfo = Marshal.PtrToStructure<SHARE_INFO_1>(curInfoPtr); - if (ignoreHidden && shareInfo.netname.EndsWith('$')) - { - continue; + if ((shareInfo.type & Interop.Windows.STYPE_MASK) != Interop.Windows.STYPE_DISKTREE) + { + continue; + } + + if (ignoreHidden && shareInfo.netname.EndsWith('$')) + { + continue; + } + + shares.Add(shareInfo.netname); } + } - shares.Add(shareInfo.netname); + return shares; + } + finally + { + if (shBuf != nint.Zero) + { + Interop.Windows.NetApiBufferFree(shBuf); } } - - return shares; #endif } @@ -6044,7 +6087,7 @@ internal static List<CompletionResult> CompleteComment(CompletionContext context for (int index = psobjs.Count - 1; index >= 0; index--) { var psobj = psobjs[index]; - if (!(PSObject.Base(psobj) is HistoryInfo historyInfo)) continue; + if (PSObject.Base(psobj) is not HistoryInfo historyInfo) continue; var commandLine = historyInfo.CommandLine; if (!string.IsNullOrEmpty(commandLine) && pattern.IsMatch(commandLine)) @@ -8545,7 +8588,7 @@ internal static bool IsPathSafelyExpandable(ExpandableStringExpressionAst expand var varValues = new List<string>(); foreach (ExpressionAst nestedAst in expandableStringAst.NestedExpressions) { - if (!(nestedAst is VariableExpressionAst variableAst)) { return false; } + if (nestedAst is not VariableExpressionAst variableAst) { return false; } string strValue = CombineVariableWithPartialPath(variableAst, null, executionContext); if (strValue != null) @@ -8613,7 +8656,7 @@ internal static string CombineVariableWithPartialPath(VariableExpressionAst vari /// <param name="parametersToAdd">The parameters to add.</param> /// <returns>Collection of command info objects.</returns> internal static Collection<CommandInfo> GetCommandInfo( - IDictionary fakeBoundParameters, + IDictionary fakeBoundParameters, params string[] parametersToAdd) { using var ps = PowerShell.Create(RunspaceMode.CurrentRunspace); @@ -8673,7 +8716,7 @@ internal static void CompleteMemberHelper( IEnumerable members; if (@static) { - if (!(PSObject.Base(value) is Type type)) + if (PSObject.Base(value) is not Type type) { return; } @@ -8739,7 +8782,7 @@ internal static void CompleteMemberHelper( var pattern = WildcardPattern.Get(memberName, WildcardOptions.IgnoreCase); foreach (DictionaryEntry entry in dictionary) { - if (!(entry.Key is string key)) + if (entry.Key is not string key) continue; if (pattern.IsMatch(key)) diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionHelpers.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionHelpers.cs index d54f4d5dc87..edf31e8e97a 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionHelpers.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionHelpers.cs @@ -129,7 +129,7 @@ internal static string NormalizeToExpandableString(string value) /// <c>true</c> if the value matches the word as a wildcard pattern; otherwise, <c>false</c>. /// </returns> /// <remarks> - /// Wildcard pattern matching allows for flexible matching, where wilcards can represent + /// Wildcard pattern matching allows for flexible matching, where wilcards can represent /// multiple characters in the input. This strategy is case-insensitive. /// </remarks> internal static readonly MatchStrategy WildcardPatternMatchIgnoreCase = (value, wordToComplete) @@ -141,11 +141,11 @@ internal static string NormalizeToExpandableString(string value) /// Determines if the given value matches the specified word considering wildcard characters literally. /// </summary> /// <returns> - /// <c>true</c> if the value matches either the literal normalized word or the wildcard pattern with escaping; + /// <c>true</c> if the value matches either the literal normalized word or the wildcard pattern with escaping; /// otherwise, <c>false</c>. /// </returns> /// <remarks> - /// This strategy first attempts a literal prefix match for performance and, if unsuccessful, escapes the word to complete to + /// This strategy first attempts a literal prefix match for performance and, if unsuccessful, escapes the word to complete to /// handle any problematic wildcard characters before performing a wildcard match. /// </remarks> internal static readonly MatchStrategy WildcardPatternEscapeMatch = (value, wordToComplete) diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionResult.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionResult.cs index ffb09e2a50d..0554a52ba17 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionResult.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionResult.cs @@ -168,26 +168,15 @@ internal static CompletionResult Null /// <param name="toolTip">The text for the tooltip with details to be displayed about the object.</param> public CompletionResult(string completionText, string listItemText, CompletionResultType resultType, string toolTip) { - if (string.IsNullOrEmpty(completionText)) - { - throw PSTraceSource.NewArgumentNullException(nameof(completionText)); - } - - if (string.IsNullOrEmpty(listItemText)) - { - throw PSTraceSource.NewArgumentNullException(nameof(listItemText)); - } + ArgumentException.ThrowIfNullOrEmpty(completionText); + ArgumentException.ThrowIfNullOrEmpty(listItemText); + ArgumentException.ThrowIfNullOrEmpty(toolTip); if (resultType < CompletionResultType.Text || resultType > CompletionResultType.DynamicKeyword) { throw PSTraceSource.NewArgumentOutOfRangeException(nameof(resultType), resultType); } - if (string.IsNullOrEmpty(toolTip)) - { - throw PSTraceSource.NewArgumentNullException(nameof(toolTip)); - } - _completionText = completionText; _listItemText = listItemText; _toolTip = toolTip; diff --git a/src/System.Management.Automation/engine/CommandCompletion/ExtensibleCompletion.cs b/src/System.Management.Automation/engine/CommandCompletion/ExtensibleCompletion.cs index 2b5281a8f26..c63a8e7f92d 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/ExtensibleCompletion.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/ExtensibleCompletion.cs @@ -172,66 +172,110 @@ public abstract class ArgumentCompleterFactoryAttribute : ArgumentCompleterAttri [Cmdlet(VerbsLifecycle.Register, "ArgumentCompleter", HelpUri = "https://go.microsoft.com/fwlink/?LinkId=528576")] public class RegisterArgumentCompleterCommand : PSCmdlet { + private const string PowerShellSetName = "PowerShellSet"; + private const string NativeCommandSetName = "NativeCommandSet"; + private const string NativeFallbackSetName = "NativeFallbackSet"; + + // Use a key that is unlikely to be a file name or path to indicate the fallback completer for native commands. + internal const string FallbackCompleterKey = "___ps::<native_fallback_key>@@___"; + /// <summary> + /// Gets or sets the command names for which the argument completer is registered. /// </summary> - [Parameter(ParameterSetName = "NativeSet", Mandatory = true)] - [Parameter(ParameterSetName = "PowerShellSet")] + [Parameter(ParameterSetName = NativeCommandSetName, Mandatory = true)] + [Parameter(ParameterSetName = PowerShellSetName)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public string[] CommandName { get; set; } /// <summary> + /// Gets or sets the name of the parameter for which the argument completer is registered. /// </summary> - [Parameter(ParameterSetName = "PowerShellSet", Mandatory = true)] + [Parameter(ParameterSetName = PowerShellSetName, Mandatory = true)] public string ParameterName { get; set; } /// <summary> + /// Gets or sets the script block that will be executed to provide argument completions. /// </summary> [Parameter(Mandatory = true)] [AllowNull()] public ScriptBlock ScriptBlock { get; set; } /// <summary> + /// Indicates the argument completer is for native commands. /// </summary> - [Parameter(ParameterSetName = "NativeSet")] + [Parameter(ParameterSetName = NativeCommandSetName)] public SwitchParameter Native { get; set; } + /// <summary> + /// Indicates the argument completer is a fallback for any native commands that don't have a completer registered. + /// </summary> + [Parameter(ParameterSetName = NativeFallbackSetName)] + public SwitchParameter NativeFallback { get; set; } + /// <summary> /// </summary> protected override void EndProcessing() { Dictionary<string, ScriptBlock> completerDictionary; - if (ParameterName != null) + + if (ParameterSetName is NativeFallbackSetName) { - completerDictionary = Context.CustomArgumentCompleters ?? - (Context.CustomArgumentCompleters = new Dictionary<string, ScriptBlock>(StringComparer.OrdinalIgnoreCase)); + completerDictionary = Context.NativeArgumentCompleters ??= new(StringComparer.OrdinalIgnoreCase); + + SetKeyValue(completerDictionary, FallbackCompleterKey, ScriptBlock); } - else + else if (ParameterSetName is NativeCommandSetName) { - completerDictionary = Context.NativeArgumentCompleters ?? - (Context.NativeArgumentCompleters = new Dictionary<string, ScriptBlock>(StringComparer.OrdinalIgnoreCase)); - } + completerDictionary = Context.NativeArgumentCompleters ??= new(StringComparer.OrdinalIgnoreCase); - if (CommandName == null || CommandName.Length == 0) + foreach (string command in CommandName) + { + var key = command?.Trim(); + if (string.IsNullOrEmpty(key)) + { + continue; + } + + SetKeyValue(completerDictionary, key, ScriptBlock); + } + } + else if (ParameterSetName is PowerShellSetName) { - CommandName = new[] { string.Empty }; + completerDictionary = Context.CustomArgumentCompleters ??= new(StringComparer.OrdinalIgnoreCase); + + string paramName = ParameterName.Trim(); + if (paramName.Length is 0) + { + return; + } + + if (CommandName is null || CommandName.Length is 0) + { + SetKeyValue(completerDictionary, paramName, ScriptBlock); + return; + } + + foreach (string command in CommandName) + { + var key = command?.Trim(); + key = string.IsNullOrEmpty(key) + ? paramName + : $"{key}:{paramName}"; + + SetKeyValue(completerDictionary, key, ScriptBlock); + } } - for (int i = 0; i < CommandName.Length; i++) + static void SetKeyValue(Dictionary<string, ScriptBlock> table, string key, ScriptBlock value) { - var key = CommandName[i]; - if (!string.IsNullOrWhiteSpace(ParameterName)) + if (value is null) { - if (!string.IsNullOrWhiteSpace(key)) - { - key = key + ":" + ParameterName; - } - else - { - key = ParameterName; - } + table.Remove(key); + } + else + { + table[key] = value; } - - completerDictionary[key] = ScriptBlock; } } } diff --git a/src/System.Management.Automation/engine/CommandDiscovery.cs b/src/System.Management.Automation/engine/CommandDiscovery.cs index cf4f561c983..9ca87f4c834 100644 --- a/src/System.Management.Automation/engine/CommandDiscovery.cs +++ b/src/System.Management.Automation/engine/CommandDiscovery.cs @@ -1231,11 +1231,17 @@ internal LookupPathCollection GetLookupDirectoryPaths() string tempDir = directory.TrimStart(); if (tempDir.EqualsOrdinalIgnoreCase("~")) { - tempDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + tempDir = Environment.GetFolderPath( + Environment.SpecialFolder.UserProfile, + Environment.SpecialFolderOption.DoNotVerify); } else if (tempDir.StartsWith("~" + Path.DirectorySeparatorChar)) { - tempDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + Path.DirectorySeparatorChar + tempDir.Substring(2); + tempDir = Environment.GetFolderPath( + Environment.SpecialFolder.UserProfile, + Environment.SpecialFolderOption.DoNotVerify) + + Path.DirectorySeparatorChar + + tempDir.Substring(2); } _cachedPath.Add(tempDir); diff --git a/src/System.Management.Automation/engine/CommandPathSearch.cs b/src/System.Management.Automation/engine/CommandPathSearch.cs index 92a533e6ec0..12c494ad8ea 100644 --- a/src/System.Management.Automation/engine/CommandPathSearch.cs +++ b/src/System.Management.Automation/engine/CommandPathSearch.cs @@ -111,7 +111,7 @@ private void ResolveCurrentDirectoryInLookupPaths() sessionState.IsProviderLoaded(fileSystemProviderName); string? environmentCurrentDirectory = null; - + try { environmentCurrentDirectory = Directory.GetCurrentDirectory(); diff --git a/src/System.Management.Automation/engine/CommandProcessor.cs b/src/System.Management.Automation/engine/CommandProcessor.cs index d9758cea2fa..d1ef250d717 100644 --- a/src/System.Management.Automation/engine/CommandProcessor.cs +++ b/src/System.Management.Automation/engine/CommandProcessor.cs @@ -101,7 +101,7 @@ internal CommandProcessor(IScriptCommandInfo scriptCommandInfo, ExecutionContext /// </exception> internal ParameterBinderController NewParameterBinderController(InternalCommand command) { - if (!(command is Cmdlet cmdlet)) + if (command is not Cmdlet cmdlet) { throw PSTraceSource.NewArgumentException(nameof(command)); } diff --git a/src/System.Management.Automation/engine/CommandSearcher.cs b/src/System.Management.Automation/engine/CommandSearcher.cs index 1d945094004..638ccd4ac79 100644 --- a/src/System.Management.Automation/engine/CommandSearcher.cs +++ b/src/System.Management.Automation/engine/CommandSearcher.cs @@ -951,7 +951,7 @@ private static bool ShouldSkipCommandResolutionForConstrainedLanguage(CommandInf if (result != null) { - var formatString = result switch + var formatString = result switch { FilterInfo => "Filter found: {0}", ConfigurationInfo => "Configuration found: {0}", diff --git a/src/System.Management.Automation/engine/CoreAdapter.cs b/src/System.Management.Automation/engine/CoreAdapter.cs index 907060bfe49..136083d04d7 100644 --- a/src/System.Management.Automation/engine/CoreAdapter.cs +++ b/src/System.Management.Automation/engine/CoreAdapter.cs @@ -1723,7 +1723,7 @@ internal static void SetReferences(object[] arguments, MethodInformation methodI // It still might be an PSObject wrapping an PSReference if (originalArgumentReference == null) { - if (!(originalArgument is PSObject originalArgumentObj)) + if (originalArgument is not PSObject originalArgumentObj) { continue; } @@ -3860,7 +3860,7 @@ internal void AddAllDynamicMembers<T>(object obj, PSMemberInfoInternalCollection private static bool PropertyIsStatic(PSProperty property) { - if (!(property.adapterData is PropertyCacheEntry entry)) + if (property.adapterData is not PropertyCacheEntry entry) { return false; } diff --git a/src/System.Management.Automation/engine/Credential.cs b/src/System.Management.Automation/engine/Credential.cs index b2be0dbfd2e..c921b9a084d 100644 --- a/src/System.Management.Automation/engine/Credential.cs +++ b/src/System.Management.Automation/engine/Credential.cs @@ -10,9 +10,6 @@ using System.Security.Cryptography; using Microsoft.PowerShell; -// FxCop suppressions for resource strings: -[module: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", Scope = "resource", Target = "Credential.resources", MessageId = "Cred")] - namespace System.Management.Automation { /// <summary> diff --git a/src/System.Management.Automation/engine/ErrorPackage.cs b/src/System.Management.Automation/engine/ErrorPackage.cs index b20da137590..6dfaf34fbec 100644 --- a/src/System.Management.Automation/engine/ErrorPackage.cs +++ b/src/System.Management.Automation/engine/ErrorPackage.cs @@ -562,7 +562,7 @@ public ErrorDetails(string message) /// <see cref="System.Resources.ResourceManager"/> /// </param> /// <param name="args"> - /// <see cref="System.String.Format(IFormatProvider,string,object[])"/> + /// <see cref="string.Format(IFormatProvider,string,object[])"/> /// insertion parameters /// </param> /// <remarks> @@ -584,7 +584,7 @@ public ErrorDetails(string message) /// by overriding virtual method /// <see cref="Cmdlet.GetResourceString"/>. /// This constructor then inserts the specified args using - /// <see cref="System.String.Format(IFormatProvider,string,object[])"/>. + /// <see cref="string.Format(IFormatProvider,string,object[])"/>. /// </remarks> public ErrorDetails( Cmdlet cmdlet, @@ -610,7 +610,7 @@ public ErrorDetails( /// <see cref="System.Resources.ResourceManager"/> /// </param> /// <param name="args"> - /// <see cref="System.String.Format(IFormatProvider,string,object[])"/> + /// <see cref="string.Format(IFormatProvider,string,object[])"/> /// insertion parameters /// </param> /// <remarks> @@ -637,7 +637,7 @@ public ErrorDetails( /// will implement /// <see cref="IResourceSupplier"/>. /// The constructor then inserts the specified args using - /// <see cref="System.String.Format(IFormatProvider,string,object[])"/>. + /// <see cref="string.Format(IFormatProvider,string,object[])"/>. /// </remarks> public ErrorDetails( IResourceSupplier resourceSupplier, @@ -663,7 +663,7 @@ public ErrorDetails( /// <see cref="System.Resources.ResourceManager"/> /// </param> /// <param name="args"> - /// <see cref="System.String.Format(IFormatProvider,string,object[])"/> + /// <see cref="string.Format(IFormatProvider,string,object[])"/> /// insertion parameters /// </param> /// <remarks> @@ -678,7 +678,7 @@ public ErrorDetails( /// This constructor first loads a template string from the assembly using /// <see cref="System.Resources.ResourceManager.GetString(string)"/>. /// The constructor then inserts the specified args using - /// <see cref="System.String.Format(IFormatProvider,string,object[])"/>. + /// <see cref="string.Format(IFormatProvider,string,object[])"/>. /// </remarks> public ErrorDetails( System.Reflection.Assembly assembly, @@ -796,7 +796,7 @@ internal Exception TextLookupError #region ToString /// <summary> - /// As <see cref="System.Object.ToString()"/> + /// As <see cref="object.ToString()"/> /// </summary> /// <returns>Developer-readable identifier.</returns> public override string ToString() @@ -1665,7 +1665,7 @@ private string GetInvocationTypeName() return commandInfo.Name; } - if (!(commandInfo is CmdletInfo cmdletInfo)) + if (commandInfo is not CmdletInfo cmdletInfo) { return string.Empty; } @@ -1677,7 +1677,7 @@ private string GetInvocationTypeName() #region ToString /// <summary> - /// As <see cref="System.Object.ToString()"/> + /// As <see cref="object.ToString()"/> /// </summary> /// <returns>Developer-readable identifier.</returns> public override string ToString() @@ -1823,7 +1823,7 @@ public interface IResourceSupplier /// if you want more complex behavior. /// /// Insertions will be inserted into the string with - /// <see cref="System.String.Format(IFormatProvider,string,object[])"/> + /// <see cref="string.Format(IFormatProvider,string,object[])"/> /// to generate the final error message in /// <see cref="ErrorDetails.Message"/>. /// </remarks> diff --git a/src/System.Management.Automation/engine/EventManager.cs b/src/System.Management.Automation/engine/EventManager.cs index 75538135b92..1c5de607321 100644 --- a/src/System.Management.Automation/engine/EventManager.cs +++ b/src/System.Management.Automation/engine/EventManager.cs @@ -1830,6 +1830,11 @@ private PSEngineEvent() { } /// </summary> public const string OnIdle = "PowerShell.OnIdle"; + /// <summary> + /// Called when <c>Debug-Runspace</c> has attached a debugger to the current runspace. + /// </summary> + public const string OnDebugAttach = "PowerShell.OnDebugAttach"; + /// <summary> /// Called during scriptblock invocation. /// </summary> @@ -1843,7 +1848,7 @@ private PSEngineEvent() { } /// <summary> /// A HashSet that contains all engine event names. /// </summary> - internal static readonly HashSet<string> EngineEvents = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { Exiting, OnIdle, OnScriptBlockInvoke }; + internal static readonly HashSet<string> EngineEvents = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { Exiting, OnIdle, OnDebugAttach, OnScriptBlockInvoke }; } /// <summary> @@ -2045,7 +2050,7 @@ public override bool Equals(object obj) { return obj is PSEventSubscriber es && Equals(es); } - + /// <summary> /// Determines if two PSEventSubscriber instances are equal /// </summary> diff --git a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs index dd26e609641..7e17ec43137 100644 --- a/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs +++ b/src/System.Management.Automation/engine/ExperimentalFeature/ExperimentalFeature.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; @@ -21,10 +20,8 @@ public class ExperimentalFeature #region Const Members internal const string EngineSource = "PSEngine"; - internal const string PSFeedbackProvider = "PSFeedbackProvider"; - internal const string PSNativeWindowsTildeExpansion = nameof(PSNativeWindowsTildeExpansion); - internal const string PSRedirectToVariable = "PSRedirectToVariable"; internal const string PSSerializeJSONLongEnumAsNumber = nameof(PSSerializeJSONLongEnumAsNumber); + internal const string PSProfileDSCResource = "PSProfileDSCResource"; #endregion @@ -107,24 +104,16 @@ static ExperimentalFeature() name: "PSFileSystemProviderV2", description: "Replace the old FileSystemProvider with cleaner design and faster code"), */ - new ExperimentalFeature( - name: "PSSubsystemPluginModel", - description: "A plugin model for registering and un-registering PowerShell subsystems"), new ExperimentalFeature( name: "PSLoadAssemblyFromNativeCode", description: "Expose an API to allow assembly loading from native code"), - new ExperimentalFeature( - name: PSFeedbackProvider, - description: "Replace the hard-coded suggestion framework with the extensible feedback provider"), - new ExperimentalFeature( - name: PSNativeWindowsTildeExpansion, - description: "On windows, expand unquoted tilde (`~`) with the user's current home folder."), - new ExperimentalFeature( - name: PSRedirectToVariable, - description: "Add support for redirecting to the variable drive"), new ExperimentalFeature( name: PSSerializeJSONLongEnumAsNumber, description: "Serialize enums based on long or ulong as an numeric value rather than the string representation when using ConvertTo-Json." + ), + new ExperimentalFeature( + name: PSProfileDSCResource, + description: "DSC v3 resources for managing PowerShell profile." ) }; diff --git a/src/System.Management.Automation/engine/GetCommandCommand.cs b/src/System.Management.Automation/engine/GetCommandCommand.cs index 2ecf19ccaa9..10e8334021b 100644 --- a/src/System.Management.Automation/engine/GetCommandCommand.cs +++ b/src/System.Management.Automation/engine/GetCommandCommand.cs @@ -1694,7 +1694,7 @@ private static PSObject GetParameterType(Type parameterType) } /// <summary> - /// Provides argument completion for Noun parameter. + /// Provides argument completion for Noun parameter. /// </summary> public class NounArgumentCompleter : IArgumentCompleter { diff --git a/src/System.Management.Automation/engine/InitialSessionState.cs b/src/System.Management.Automation/engine/InitialSessionState.cs index 84f513d8450..a6d50f47443 100644 --- a/src/System.Management.Automation/engine/InitialSessionState.cs +++ b/src/System.Management.Automation/engine/InitialSessionState.cs @@ -2124,7 +2124,7 @@ public virtual HashSet<string> StartupScripts } } - private HashSet<string> _startupScripts = new HashSet<string>(); + private HashSet<string> _startupScripts; private readonly object _syncObject = new object(); @@ -4527,6 +4527,14 @@ static InitialSessionState() ScopedItemOptions.None, new ArgumentTypeConverterAttribute(typeof(System.Text.Encoding))), + // Variable which controls the encoding for decoding data from a NativeCommand + new SessionStateVariableEntry( + SpecialVariables.PSApplicationOutputEncoding, + null, + RunspaceInit.PSApplicationOutputEncodingDescription, + ScopedItemOptions.None, + new ArgumentTypeConverterAttribute(typeof(Encoding))), + // Preferences // // NTRAID#Windows Out Of Band Releases-931461-2006/03/13 @@ -5470,6 +5478,7 @@ private static void InitializeCoreCmdletsAndProviders( { "Get-Module", new SessionStateCmdletEntry("Get-Module", typeof(GetModuleCommand), helpFile) }, { "Get-PSHostProcessInfo", new SessionStateCmdletEntry("Get-PSHostProcessInfo", typeof(GetPSHostProcessInfoCommand), helpFile) }, { "Get-PSSession", new SessionStateCmdletEntry("Get-PSSession", typeof(GetPSSessionCommand), helpFile) }, + { "Get-PSSubsystem", new SessionStateCmdletEntry("Get-PSSubsystem", typeof(Subsystem.GetPSSubsystemCommand), helpFile) }, { "Import-Module", new SessionStateCmdletEntry("Import-Module", typeof(ImportModuleCommand), helpFile) }, { "Invoke-Command", new SessionStateCmdletEntry("Invoke-Command", typeof(InvokeCommandCommand), helpFile) }, { "Invoke-History", new SessionStateCmdletEntry("Invoke-History", typeof(InvokeHistoryCommand), helpFile) }, @@ -5510,11 +5519,6 @@ private static void InitializeCoreCmdletsAndProviders( { "Format-Default", new SessionStateCmdletEntry("Format-Default", typeof(FormatDefaultCommand), helpFile) }, }; - if (ExperimentalFeature.IsEnabled("PSSubsystemPluginModel")) - { - cmdlets.Add("Get-PSSubsystem", new SessionStateCmdletEntry("Get-PSSubsystem", typeof(Subsystem.GetPSSubsystemCommand), helpFile)); - } - #if UNIX cmdlets.Add("Switch-Process", new SessionStateCmdletEntry("Switch-Process", typeof(SwitchProcessCommand), helpFile)); #endif diff --git a/src/System.Management.Automation/engine/InternalCommands.cs b/src/System.Management.Automation/engine/InternalCommands.cs index 39556db9cca..4e50e2d130f 100644 --- a/src/System.Management.Automation/engine/InternalCommands.cs +++ b/src/System.Management.Automation/engine/InternalCommands.cs @@ -1441,8 +1441,11 @@ public SwitchParameter EQ set { - _binaryOperator = TokenKind.Ieq; - _forceBooleanEvaluation = false; + if (value) + { + _binaryOperator = TokenKind.Ieq; + _forceBooleanEvaluation = false; + } } } @@ -1459,7 +1462,10 @@ public SwitchParameter CEQ set { - _binaryOperator = TokenKind.Ceq; + if (value) + { + _binaryOperator = TokenKind.Ceq; + } } } @@ -1477,7 +1483,10 @@ public SwitchParameter NE set { - _binaryOperator = TokenKind.Ine; + if (value) + { + _binaryOperator = TokenKind.Ine; + } } } @@ -1494,7 +1503,10 @@ public SwitchParameter CNE set { - _binaryOperator = TokenKind.Cne; + if (value) + { + _binaryOperator = TokenKind.Cne; + } } } @@ -1512,7 +1524,10 @@ public SwitchParameter GT set { - _binaryOperator = TokenKind.Igt; + if (value) + { + _binaryOperator = TokenKind.Igt; + } } } @@ -1529,7 +1544,10 @@ public SwitchParameter CGT set { - _binaryOperator = TokenKind.Cgt; + if (value) + { + _binaryOperator = TokenKind.Cgt; + } } } @@ -1547,7 +1565,10 @@ public SwitchParameter LT set { - _binaryOperator = TokenKind.Ilt; + if (value) + { + _binaryOperator = TokenKind.Ilt; + } } } @@ -1564,7 +1585,10 @@ public SwitchParameter CLT set { - _binaryOperator = TokenKind.Clt; + if (value) + { + _binaryOperator = TokenKind.Clt; + } } } @@ -1582,7 +1606,10 @@ public SwitchParameter GE set { - _binaryOperator = TokenKind.Ige; + if (value) + { + _binaryOperator = TokenKind.Ige; + } } } @@ -1599,7 +1626,10 @@ public SwitchParameter CGE set { - _binaryOperator = TokenKind.Cge; + if (value) + { + _binaryOperator = TokenKind.Cge; + } } } @@ -1617,7 +1647,10 @@ public SwitchParameter LE set { - _binaryOperator = TokenKind.Ile; + if (value) + { + _binaryOperator = TokenKind.Ile; + } } } @@ -1634,7 +1667,10 @@ public SwitchParameter CLE set { - _binaryOperator = TokenKind.Cle; + if (value) + { + _binaryOperator = TokenKind.Cle; + } } } @@ -1652,7 +1688,10 @@ public SwitchParameter Like set { - _binaryOperator = TokenKind.Ilike; + if (value) + { + _binaryOperator = TokenKind.Ilike; + } } } @@ -1669,7 +1708,10 @@ public SwitchParameter CLike set { - _binaryOperator = TokenKind.Clike; + if (value) + { + _binaryOperator = TokenKind.Clike; + } } } @@ -1687,7 +1729,10 @@ public SwitchParameter NotLike set { - _binaryOperator = TokenKind.Inotlike; + if (value) + { + _binaryOperator = TokenKind.Inotlike; + } } } @@ -1704,7 +1749,10 @@ public SwitchParameter CNotLike set { - _binaryOperator = TokenKind.Cnotlike; + if (value) + { + _binaryOperator = TokenKind.Cnotlike; + } } } @@ -1722,7 +1770,10 @@ public SwitchParameter Match set { - _binaryOperator = TokenKind.Imatch; + if (value) + { + _binaryOperator = TokenKind.Imatch; + } } } @@ -1739,7 +1790,10 @@ public SwitchParameter CMatch set { - _binaryOperator = TokenKind.Cmatch; + if (value) + { + _binaryOperator = TokenKind.Cmatch; + } } } @@ -1757,7 +1811,10 @@ public SwitchParameter NotMatch set { - _binaryOperator = TokenKind.Inotmatch; + if (value) + { + _binaryOperator = TokenKind.Inotmatch; + } } } @@ -1774,7 +1831,10 @@ public SwitchParameter CNotMatch set { - _binaryOperator = TokenKind.Cnotmatch; + if (value) + { + _binaryOperator = TokenKind.Cnotmatch; + } } } @@ -1792,7 +1852,10 @@ public SwitchParameter Contains set { - _binaryOperator = TokenKind.Icontains; + if (value) + { + _binaryOperator = TokenKind.Icontains; + } } } @@ -1809,7 +1872,10 @@ public SwitchParameter CContains set { - _binaryOperator = TokenKind.Ccontains; + if (value) + { + _binaryOperator = TokenKind.Ccontains; + } } } @@ -1827,7 +1893,10 @@ public SwitchParameter NotContains set { - _binaryOperator = TokenKind.Inotcontains; + if (value) + { + _binaryOperator = TokenKind.Inotcontains; + } } } @@ -1844,7 +1913,10 @@ public SwitchParameter CNotContains set { - _binaryOperator = TokenKind.Cnotcontains; + if (value) + { + _binaryOperator = TokenKind.Cnotcontains; + } } } @@ -1862,7 +1934,10 @@ public SwitchParameter In set { - _binaryOperator = TokenKind.In; + if (value) + { + _binaryOperator = TokenKind.In; + } } } @@ -1879,7 +1954,10 @@ public SwitchParameter CIn set { - _binaryOperator = TokenKind.Cin; + if (value) + { + _binaryOperator = TokenKind.Cin; + } } } @@ -1897,7 +1975,10 @@ public SwitchParameter NotIn set { - _binaryOperator = TokenKind.Inotin; + if (value) + { + _binaryOperator = TokenKind.Inotin; + } } } @@ -1914,7 +1995,10 @@ public SwitchParameter CNotIn set { - _binaryOperator = TokenKind.Cnotin; + if (value) + { + _binaryOperator = TokenKind.Cnotin; + } } } @@ -1931,7 +2015,10 @@ public SwitchParameter Is set { - _binaryOperator = TokenKind.Is; + if (value) + { + _binaryOperator = TokenKind.Is; + } } } @@ -1948,7 +2035,10 @@ public SwitchParameter IsNot set { - _binaryOperator = TokenKind.IsNot; + if (value) + { + _binaryOperator = TokenKind.IsNot; + } } } @@ -1965,7 +2055,10 @@ public SwitchParameter Not set { - _binaryOperator = TokenKind.Not; + if (value) + { + _binaryOperator = TokenKind.Not; + } } } @@ -2018,7 +2111,7 @@ private void CheckLanguageMode() private object GetLikeRHSOperand(object operand) { - if (!(operand is string val)) + if (operand is not string val) { return operand; } diff --git a/src/System.Management.Automation/engine/Interop/Windows/NetApiBufferFree.cs b/src/System.Management.Automation/engine/Interop/Windows/NetApiBufferFree.cs new file mode 100644 index 00000000000..4f7d0541872 --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/NetApiBufferFree.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static unsafe partial class Windows + { + + [LibraryImport("Netapi32.dll")] + internal static partial uint NetApiBufferFree(nint Buffer); + } +} diff --git a/src/System.Management.Automation/engine/Interop/Windows/QueryDosDevice.cs b/src/System.Management.Automation/engine/Interop/Windows/QueryDosDevice.cs index 27907bd0135..d23899e8ef7 100644 --- a/src/System.Management.Automation/engine/Interop/Windows/QueryDosDevice.cs +++ b/src/System.Management.Automation/engine/Interop/Windows/QueryDosDevice.cs @@ -28,7 +28,7 @@ internal static string GetDosDeviceForNetworkPath(char deviceName) #endif Span<char> buffer = stackalloc char[StartLength + 1]; - Span<char> fullDeviceName = stackalloc char[3] { deviceName, ':', '\0' }; + Span<char> fullDeviceName = [deviceName, ':', '\0']; char[]? rentedArray = null; try diff --git a/src/System.Management.Automation/engine/Interop/Windows/WNetGetConnection.cs b/src/System.Management.Automation/engine/Interop/Windows/WNetGetConnection.cs index 88ec4386a14..01667fb4274 100644 --- a/src/System.Management.Automation/engine/Interop/Windows/WNetGetConnection.cs +++ b/src/System.Management.Automation/engine/Interop/Windows/WNetGetConnection.cs @@ -25,7 +25,7 @@ internal static int GetUNCForNetworkDrive(char drive, out string? uncPath) return ERROR_NOT_SUPPORTED; } - ReadOnlySpan<char> driveName = stackalloc char[] { drive, ':', '\0' }; + ReadOnlySpan<char> driveName = [drive, ':', '\0']; int bufferSize = MAX_PATH; Span<char> uncBuffer = stackalloc char[MAX_PATH]; if (InternalTestHooks.WNetGetConnectionBufferSize > 0 && InternalTestHooks.WNetGetConnectionBufferSize <= MAX_PATH) diff --git a/src/System.Management.Automation/engine/LanguagePrimitives.cs b/src/System.Management.Automation/engine/LanguagePrimitives.cs index 6644223d7c6..13d8f66e5e9 100644 --- a/src/System.Management.Automation/engine/LanguagePrimitives.cs +++ b/src/System.Management.Automation/engine/LanguagePrimitives.cs @@ -636,7 +636,7 @@ public static bool Equals(object first, object second, bool ignoreCase, IFormatP formatProvider ??= CultureInfo.InvariantCulture; - if (!(formatProvider is CultureInfo culture)) + if (formatProvider is not CultureInfo culture) { throw PSTraceSource.NewArgumentException(nameof(formatProvider)); } @@ -780,7 +780,7 @@ public static int Compare(object first, object second, bool ignoreCase, IFormatP { formatProvider ??= CultureInfo.InvariantCulture; - if (!(formatProvider is CultureInfo culture)) + if (formatProvider is not CultureInfo culture) { throw PSTraceSource.NewArgumentException(nameof(formatProvider)); } @@ -1036,7 +1036,7 @@ internal static bool IsTrue(IList objectArray) // but since we don't want this to recurse indefinitely // we explicitly check the case where it would recurse // and deal with it. - if (!(PSObject.Base(objectArray[0]) is IList firstElement)) + if (PSObject.Base(objectArray[0]) is not IList firstElement) { return IsTrue(objectArray[0]); } @@ -2100,7 +2100,7 @@ public override object ConvertFrom(object sourceValue, Type destinationType, IFo protected static object BaseConvertFrom(object sourceValue, Type destinationType, IFormatProvider formatProvider, bool ignoreCase, bool multipleValues) { Diagnostics.Assert(sourceValue != null, "the type converter has a special case for null source values"); - if (!(sourceValue is string sourceValueString)) + if (sourceValue is not string sourceValueString) { throw new PSInvalidCastException("InvalidCastEnumFromTypeNotAString", null, ExtendedTypeSystem.InvalidCastException, @@ -2943,9 +2943,9 @@ private static object ConvertStringToInteger( } if (resultType == typeof(BigInteger)) - { + { NumberStyles style = NumberStyles.Integer | NumberStyles.AllowThousands; - + return BigInteger.Parse(strToConvert, style, NumberFormatInfo.InvariantInfo); } // Fallback conversion for regular numeric types. diff --git a/src/System.Management.Automation/engine/ManagementObjectAdapter.cs b/src/System.Management.Automation/engine/ManagementObjectAdapter.cs index aa1c151e0e3..9b69ee168d7 100644 --- a/src/System.Management.Automation/engine/ManagementObjectAdapter.cs +++ b/src/System.Management.Automation/engine/ManagementObjectAdapter.cs @@ -172,7 +172,7 @@ protected override T GetMember<T>(object obj, string memberName) { tracer.WriteLine("Getting member with name {0}", memberName); - if (!(obj is ManagementBaseObject mgmtObject)) + if (obj is not ManagementBaseObject mgmtObject) { return null; } @@ -364,7 +364,7 @@ protected override object PropertyGet(PSProperty property) /// <param name="convertIfPossible">Instructs the adapter to convert before setting, if the adapter supports conversion.</param> protected override void PropertySet(PSProperty property, object setValue, bool convertIfPossible) { - if (!(property.baseObject is ManagementBaseObject mObj)) + if (property.baseObject is not ManagementBaseObject mObj) { throw new SetValueInvocationException("CannotSetNonManagementObjectMsg", null, diff --git a/src/System.Management.Automation/engine/Modules/AnalysisCache.cs b/src/System.Management.Automation/engine/Modules/AnalysisCache.cs index 44dc9f1b610..8a5f29e2b16 100644 --- a/src/System.Management.Automation/engine/Modules/AnalysisCache.cs +++ b/src/System.Management.Automation/engine/Modules/AnalysisCache.cs @@ -194,7 +194,7 @@ internal static bool ModuleIsEditionIncompatible(string modulePath, Hashtable mo internal static bool ModuleAnalysisViaGetModuleRequired(object modulePathObj, bool hadCmdlets, bool hadFunctions, bool hadAliases) { - if (!(modulePathObj is string modulePath)) + if (modulePathObj is not string modulePath) return true; if (modulePath.EndsWith(StringLiterals.PowerShellModuleFileExtension, StringComparison.OrdinalIgnoreCase)) @@ -256,7 +256,7 @@ private static bool CheckModulesTypesInManifestAgainstExportedCommands(Hashtable return ModuleAnalysisViaGetModuleRequired(nestedModule, hadCmdlets, hadFunctions, hadAliases); } - if (!(nestedModules is object[] nestedModuleArray)) + if (nestedModules is not object[] nestedModuleArray) return true; foreach (var element in nestedModuleArray) @@ -664,6 +664,11 @@ private static byte[] GetHeader() public void QueueSerialization() { + if (string.IsNullOrEmpty(s_cacheStoreLocation)) + { + return; + } + // We expect many modules to rapidly call for serialization. // Instead of doing it right away, we'll queue a task that starts writing // after it seems like we've stopped adding stuff to write out. This is @@ -1121,7 +1126,7 @@ static AnalysisCacheData() cacheFileName = string.Create(CultureInfo.InvariantCulture, $"{cacheFileName}-{hashString}"); } - s_cacheStoreLocation = Path.Combine(Platform.CacheDirectory, cacheFileName); + Platform.TryDeriveFromCache(cacheFileName, out s_cacheStoreLocation); } } diff --git a/src/System.Management.Automation/engine/Modules/GetModuleCommand.cs b/src/System.Management.Automation/engine/Modules/GetModuleCommand.cs index ca48c5c698c..caf7527d73e 100644 --- a/src/System.Management.Automation/engine/Modules/GetModuleCommand.cs +++ b/src/System.Management.Automation/engine/Modules/GetModuleCommand.cs @@ -297,29 +297,17 @@ protected override void StopProcessing() #region IDisposable Members /// <summary> - /// Releases resources associated with this object. + /// Release all resources. /// </summary> public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// <summary> - /// Releases resources associated with this object. - /// </summary> - private void Dispose(bool disposing) { if (_disposed) { return; } - if (disposing) - { - _cancellationTokenSource.Dispose(); - } - + _cancellationTokenSource.Dispose(); + _disposed = true; } @@ -604,7 +592,7 @@ public IEnumerable<CompletionResult> CompleteArgument( string parameterName, string wordToComplete, CommandAst commandAst, - IDictionary fakeBoundParameters) + IDictionary fakeBoundParameters) => CompletionHelpers.GetMatchingResults(wordToComplete, possibleCompletionValues: Utils.AllowedEditionValues); } } diff --git a/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs b/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs index 15e8bfe6a9a..532079d1a77 100644 --- a/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs +++ b/src/System.Management.Automation/engine/Modules/ImportModuleCommand.cs @@ -1371,7 +1371,7 @@ private void ImportModule_RemotelyViaCimSession( } } - private bool IsPs1xmlFileHelper_IsPresentInEntries(RemoteDiscoveryHelper.CimModuleFile cimModuleFile, IEnumerable<string> manifestEntries) + private bool IsPs1xmlFileHelper_IsPresentInEntries(RemoteDiscoveryHelper.CimModuleFile cimModuleFile, List<string> manifestEntries) { const string ps1xmlExt = ".ps1xml"; string fileName = cimModuleFile.FileName; @@ -1780,28 +1780,16 @@ protected override void StopProcessing() #region IDisposable Members /// <summary> - /// Releases resources associated with this object. + /// Release all resources. /// </summary> public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// <summary> - /// Releases resources associated with this object. - /// </summary> - private void Dispose(bool disposing) { if (_disposed) { return; } - if (disposing) - { - _cancellationTokenSource.Dispose(); - } + _cancellationTokenSource.Dispose(); _disposed = true; } diff --git a/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs b/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs index f07ddcc6335..0a1d0bfc04f 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs @@ -6168,7 +6168,7 @@ private void CheckForDisallowedDotSourcing( } } } - + private static void RemoveNestedModuleFunctions( ExecutionContext context, PSModuleInfo module, @@ -7276,7 +7276,7 @@ private static void ImportFunctions(FunctionInfo func, SessionStateInternal targ targetSessionState.ExecutionContext); // Note that the module 'func' and the function table 'functionInfo' instances are now linked - // together (see 'CopiedCommand' in CommandInfo class), so setting visibility on one also + // together (see 'CopiedCommand' in CommandInfo class), so setting visibility on one also // sets it on the other. SetCommandVisibility(isImportModulePrivate, functionInfo); functionInfo.Module = sourceModule; diff --git a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs index b687e502763..538c4775f0a 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs @@ -10,9 +10,7 @@ using System.Management.Automation.Language; using System.Text; using System.Threading; - using Microsoft.PowerShell.Commands; -using Microsoft.Win32; using Dbg = System.Management.Automation.Diagnostics; @@ -39,18 +37,22 @@ public class ModuleIntrinsics private static readonly string s_windowsPowerShellPSHomeModulePath = Path.Combine(System.Environment.SystemDirectory, "WindowsPowerShell", "v1.0", "Modules"); + static ModuleIntrinsics() + { + // Initialize the module path. + SetModulePath(); + } + internal ModuleIntrinsics(ExecutionContext context) { _context = context; - - // And initialize the module path... - SetModulePath(); + ModuleTable = new Dictionary<string, PSModuleInfo>(StringComparer.OrdinalIgnoreCase); } private readonly ExecutionContext _context; // Holds the module collection... - internal Dictionary<string, PSModuleInfo> ModuleTable { get; } = new Dictionary<string, PSModuleInfo>(StringComparer.OrdinalIgnoreCase); + internal Dictionary<string, PSModuleInfo> ModuleTable { get; } private const int MaxModuleNestingDepth = 10; @@ -176,11 +178,9 @@ private PSModuleInfo CreateModuleImplementation(string name, string path, object sb.SessionState = ss; } - else + else if (moduleCode is string sbText) { - var sbText = moduleCode as string; - if (sbText != null) - sb = ScriptBlock.Create(_context, sbText); + sb = ScriptBlock.Create(_context, sbText); } } @@ -967,7 +967,9 @@ internal static string GetPersonalModulePath() #if UNIX return Platform.SelectProductNameForDirectory(Platform.XDG_Type.USER_MODULES); #else - string myDocumentsPath = InternalTestHooks.SetMyDocumentsSpecialFolderToBlank ? string.Empty : Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); + string myDocumentsPath = InternalTestHooks.SetMyDocumentsSpecialFolderToBlank + ? string.Empty + : Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments, Environment.SpecialFolderOption.DoNotVerify); return string.IsNullOrEmpty(myDocumentsPath) ? null : Path.Combine(myDocumentsPath, Utils.ModuleDirectory); #endif } @@ -1082,91 +1084,80 @@ internal static string GetExpandedEnvironmentVariable(string name, EnvironmentVa return result; } - /// <summary> - /// Checks if a particular string (path) is a member of 'combined path' string (like %Path% or %PSModulePath%) - /// </summary> - /// <param name="pathToScan">'Combined path' string to analyze; can not be null.</param> - /// <param name="pathToLookFor">Path to search for; can not be another 'combined path' (semicolon-separated); can not be null.</param> - /// <returns>Index of pathToLookFor in pathToScan; -1 if not found.</returns> - private static int PathContainsSubstring(string pathToScan, string pathToLookFor) - { - // we don't support if any of the args are null - parent function should ensure this; empty values are ok - Diagnostics.Assert(pathToScan != null, "pathToScan should not be null according to contract of the function"); - Diagnostics.Assert(pathToLookFor != null, "pathToLookFor should not be null according to contract of the function"); - - int pos = 0; // position of the current substring in pathToScan - string[] substrings = pathToScan.Split(Path.PathSeparator, StringSplitOptions.None); // we want to process empty entries - string goodPathToLookFor = pathToLookFor.Trim().TrimEnd(Path.DirectorySeparatorChar); // trailing backslashes and white-spaces will mess up equality comparison - foreach (string substring in substrings) - { - string goodSubstring = substring.Trim().TrimEnd(Path.DirectorySeparatorChar); // trailing backslashes and white-spaces will mess up equality comparison - - // We have to use equality comparison on individual substrings (as opposed to simple 'string.IndexOf' or 'string.Contains') - // because of cases like { pathToScan="C:\Temp\MyDir\MyModuleDir", pathToLookFor="C:\Temp" } - - if (string.Equals(goodSubstring, goodPathToLookFor, StringComparison.OrdinalIgnoreCase)) - { - return pos; // match found - return index of it in the 'pathToScan' string - } - else - { - pos += substring.Length + 1; // '1' is for trailing semicolon - } - } - // if we are here, that means a match was not found - return -1; - } - /// <summary> /// Adds paths to a 'combined path' string (like %Path% or %PSModulePath%) if they are not already there. /// </summary> /// <param name="basePath">Path string (like %Path% or %PSModulePath%).</param> - /// <param name="pathToAdd">Collection of individual paths to add.</param> + /// <param name="pathToAdd">An individual path to add, or multiple paths separated by the path separator character.</param> /// <param name="insertPosition">-1 to append to the end; 0 to insert in the beginning of the string; etc...</param> /// <returns>Result string.</returns> - private static string AddToPath(string basePath, string pathToAdd, int insertPosition) + private static string UpdatePath(string basePath, string pathToAdd, ref int insertPosition) { // we don't support if any of the args are null - parent function should ensure this; empty values are ok - Diagnostics.Assert(basePath != null, "basePath should not be null according to contract of the function"); - Diagnostics.Assert(pathToAdd != null, "pathToAdd should not be null according to contract of the function"); + Dbg.Assert(basePath != null, "basePath should not be null according to contract of the function"); + Dbg.Assert(pathToAdd != null, "pathToAdd should not be null according to contract of the function"); + + // The 'pathToAdd' could be a 'combined path' (path-separator-separated). + string[] newPaths = pathToAdd.Split( + Path.PathSeparator, + StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - StringBuilder result = new StringBuilder(basePath); + if (newPaths.Length is 0) + { + // The 'pathToAdd' doesn't really contain any paths to add. + return basePath; + } + + var result = new StringBuilder(basePath, capacity: basePath.Length + pathToAdd.Length + newPaths.Length); + var addedPaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase); + string[] initialPaths = basePath.Split( + Path.PathSeparator, + StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + + foreach (string p in initialPaths) + { + // Remove the trailing directory separators. + // Trailing white spaces were already removed by 'StringSplitOptions.TrimEntries'. + addedPaths.Add(Path.TrimEndingDirectorySeparator(p)); + } - if (!string.IsNullOrEmpty(pathToAdd)) // we don't want to append empty paths + foreach (string subPathToAdd in newPaths) { - foreach (string subPathToAdd in pathToAdd.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries)) // in case pathToAdd is a 'combined path' (semicolon-separated) + // Remove the trailing directory separators. + // Trailing white spaces were already removed by 'StringSplitOptions.TrimEntries'. + string normalizedPath = Path.TrimEndingDirectorySeparator(subPathToAdd); + if (addedPaths.Contains(normalizedPath)) { - int position = PathContainsSubstring(result.ToString(), subPathToAdd); // searching in effective 'result' value ensures that possible duplicates in pathsToAdd are handled correctly - if (position == -1) // subPathToAdd not found - add it - { - if (insertPosition == -1 || insertPosition > basePath.Length) // append subPathToAdd to the end - { - bool endsWithPathSeparator = false; - if (result.Length > 0) - { - endsWithPathSeparator = (result[result.Length - 1] == Path.PathSeparator); - } + // The normalized sub path was already added - skip it. + continue; + } - if (endsWithPathSeparator) - { - result.Append(subPathToAdd); - } - else - { - result.Append(Path.PathSeparator + subPathToAdd); - } - } - else if (insertPosition > result.Length) - { - // handle case where path is a singleton with no path separator already - result.Append(Path.PathSeparator).Append(subPathToAdd); - } - else // insert at the requested location (this is used by DSC (<Program Files> location) and by 'user-specific location' (SpecialFolder.MyDocuments or EVT.User)) - { - result.Insert(insertPosition, subPathToAdd + Path.PathSeparator); - } + // The normalized sub path was not found - add it. + if (insertPosition is -1 || insertPosition >= result.Length) + { + // Append the normalized sub path to the end. + if (result.Length > 0 && result[^1] != Path.PathSeparator) + { + result.Append(Path.PathSeparator); } + + result.Append(normalizedPath); + // Next insertion should happen at the end. + insertPosition = result.Length; + } + else + { + // Insert at the requested location. + // This is used by the user-specific module path, the shared module path (<Program Files> location), and the PSHome module path. + string strToInsert = normalizedPath + Path.PathSeparator; + result.Insert(insertPosition, strToInsert); + + // Next insertion should happen after the just inserted string. + insertPosition += strToInsert.Length; } + + // Add it to the set. + addedPaths.Add(normalizedPath); } return result.ToString(); @@ -1218,7 +1209,7 @@ public static string GetModulePath(string currentProcessModulePath, string hklmM string psHomeModulePath = GetPSHomeModulePath(); // $PSHome\Modules location // If the variable isn't set, then set it to the default value - if (currentProcessModulePath == null) // EVT.Process does Not exist - really corner case + if (string.IsNullOrEmpty(currentProcessModulePath)) // EVT.Process does Not exist - really corner case { // Handle the default case... if (string.IsNullOrEmpty(hkcuUserModulePath)) // EVT.User does Not exist -> set to <SpecialFolder.MyDocuments> location @@ -1270,21 +1261,6 @@ public static string GetModulePath(string currentProcessModulePath, string hklmM return currentProcessModulePath; } - private static string UpdatePath(string path, string pathToAdd, ref int insertIndex) - { - if (!string.IsNullOrEmpty(pathToAdd)) - { - path = AddToPath(path, pathToAdd, insertIndex); - insertIndex = path.IndexOf(Path.PathSeparator, PathContainsSubstring(path, pathToAdd)); - if (insertIndex != -1) - { - // advance past the path separator - insertIndex++; - } - } - return path; - } - /// <summary> /// Checks if $env:PSModulePath is not set and sets it as appropriate. Note - because these /// strings go through the provider, we need to escape any wildcards before passing them @@ -1358,11 +1334,16 @@ private static string SetModulePath() { string currentModulePath = GetExpandedEnvironmentVariable(Constants.PSModulePathEnvVar, EnvironmentVariableTarget.Process); #if !UNIX - // if the current process and user env vars are the same, it means we need to append the machine one as it's incomplete - // otherwise, the user modified it and we should use the process one + // if the current process and user env vars are the same, it means we need to append the machine one as it's incomplete. + // Otherwise, the user modified it and we should use the process one. if (string.CompareOrdinal(GetExpandedEnvironmentVariable(Constants.PSModulePathEnvVar, EnvironmentVariableTarget.User), currentModulePath) == 0) { - currentModulePath = currentModulePath + Path.PathSeparator + GetExpandedEnvironmentVariable(Constants.PSModulePathEnvVar, EnvironmentVariableTarget.Machine); + string machineScopeValue = GetExpandedEnvironmentVariable(Constants.PSModulePathEnvVar, EnvironmentVariableTarget.Machine); + currentModulePath = string.IsNullOrEmpty(currentModulePath) + ? machineScopeValue + : string.IsNullOrEmpty(machineScopeValue) + ? currentModulePath + : string.Concat(currentModulePath, Path.PathSeparator, machineScopeValue); } #endif string allUsersModulePath = PowerShellConfig.Instance.GetModulePath(ConfigScope.AllUsers); diff --git a/src/System.Management.Automation/engine/Modules/ScriptAnalysis.cs b/src/System.Management.Automation/engine/Modules/ScriptAnalysis.cs index 7207a7292c0..892d7375387 100644 --- a/src/System.Management.Automation/engine/Modules/ScriptAnalysis.cs +++ b/src/System.Management.Automation/engine/Modules/ScriptAnalysis.cs @@ -40,7 +40,7 @@ internal static ScriptAnalysis Analyze(string path, ExecutionContext context) // So eat the invalid operation } - string scriptContent = ReadScript(path); + string scriptContent = File.ReadAllText(path, Encoding.Default); ParseError[] errors; var moduleAst = (new Parser()).Parse(path, scriptContent, null, out errors, ParseMode.ModuleAnalysis); @@ -89,19 +89,6 @@ internal static ScriptAnalysis Analyze(string path, ExecutionContext context) return result; } - internal static string ReadScript(string path) - { - using (FileStream readerStream = new FileStream(path, FileMode.Open, FileAccess.Read)) - { - Microsoft.Win32.SafeHandles.SafeFileHandle safeFileHandle = readerStream.SafeFileHandle; - - using (StreamReader scriptReader = new StreamReader(readerStream, Encoding.Default)) - { - return scriptReader.ReadToEnd(); - } - } - } - internal List<string> DiscoveredExports { get; set; } internal Dictionary<string, string> DiscoveredAliases { get; set; } diff --git a/src/System.Management.Automation/engine/MshCommandRuntime.cs b/src/System.Management.Automation/engine/MshCommandRuntime.cs index 883c800deab..e674cb1c5eb 100644 --- a/src/System.Management.Automation/engine/MshCommandRuntime.cs +++ b/src/System.Management.Automation/engine/MshCommandRuntime.cs @@ -2360,7 +2360,7 @@ internal AllowWrite(InternalCommand permittedToWrite, bool permittedToWriteToPip { if (permittedToWrite == null) throw PSTraceSource.NewArgumentNullException(nameof(permittedToWrite)); - if (!(permittedToWrite.commandRuntime is MshCommandRuntime mcr)) + if (permittedToWrite.commandRuntime is not MshCommandRuntime mcr) throw PSTraceSource.NewArgumentNullException("permittedToWrite.CommandRuntime"); _pp = mcr.PipelineProcessor; if (_pp == null) @@ -2372,19 +2372,18 @@ internal AllowWrite(InternalCommand permittedToWrite, bool permittedToWriteToPip _pp._permittedToWriteToPipeline = permittedToWriteToPipeline; _pp._permittedToWriteThread = Thread.CurrentThread; } + /// <summary> - /// End the scope where WriteObject/WriteError is permitted. + /// Release all resources. /// </summary> - /// <!-- - /// Not a true public, since the class is internal. - /// This is public only due to C# interface rules. - /// --> + /// <remarks> + /// End the scope where WriteObject/WriteError is permitted. + /// </remarks> public void Dispose() { _pp._permittedToWrite = _wasPermittedToWrite; _pp._permittedToWriteToPipeline = _wasPermittedToWriteToPipeline; _pp._permittedToWriteThread = _wasPermittedToWriteThread; - GC.SuppressFinalize(this); } // There is no finalizer, by design. This class relies on always @@ -2992,21 +2991,19 @@ internal ConfirmImpact ConfirmPreference { // WhatIf not relevant, it never gets this far in that case if (Confirm) - return ConfirmImpact.Low; - if (Debug) { - if (IsConfirmFlagSet) // -Debug -Confirm:$false - return ConfirmImpact.None; return ConfirmImpact.Low; } - if (IsConfirmFlagSet) // -Confirm:$false + if (IsConfirmFlagSet) + { + // -Confirm:$false return ConfirmImpact.None; + } if (!_isConfirmPreferenceCached) { - bool defaultUsed = false; - _confirmPreference = Context.GetEnumPreference(SpecialVariables.ConfirmPreferenceVarPath, _confirmPreference, out defaultUsed); + _confirmPreference = Context.GetEnumPreference(SpecialVariables.ConfirmPreferenceVarPath, _confirmPreference, out _); _isConfirmPreferenceCached = true; } diff --git a/src/System.Management.Automation/engine/MshObject.cs b/src/System.Management.Automation/engine/MshObject.cs index 8303058759d..c5e8bbac443 100644 --- a/src/System.Management.Automation/engine/MshObject.cs +++ b/src/System.Management.Automation/engine/MshObject.cs @@ -592,7 +592,7 @@ protected PSObject(SerializationInfo info, StreamingContext context) throw PSTraceSource.NewArgumentNullException(nameof(info)); } - if (!(info.GetValue("CliXml", typeof(string)) is string serializedData)) + if (info.GetValue("CliXml", typeof(string)) is not string serializedData) { throw PSTraceSource.NewArgumentNullException(nameof(info)); } @@ -980,7 +980,7 @@ public static implicit operator PSObject(bool valueToConvert) /// </summary> internal static object Base(object obj) { - if (!(obj is PSObject mshObj)) + if (obj is not PSObject mshObj) { return obj; } @@ -1064,7 +1064,7 @@ internal static PSObject AsPSObject(object obj, bool storeTypeNameAndInstanceMem /// <returns></returns> internal static object GetKeyForResurrectionTables(object obj) { - if (!(obj is PSObject pso)) + if (obj is not PSObject pso) { return obj; } @@ -1611,7 +1611,7 @@ public virtual PSObject Copy() bool needToReAddInstanceMembersAndTypeNames = !object.ReferenceEquals(GetKeyForResurrectionTables(this), GetKeyForResurrectionTables(returnValue)); if (needToReAddInstanceMembersAndTypeNames) { - Diagnostics.Assert(!returnValue.InstanceMembers.Any(), "needToReAddInstanceMembersAndTypeNames should mean that the new object has a fresh/empty list of instance members"); + Diagnostics.Assert(returnValue.InstanceMembers.Count == 0, "needToReAddInstanceMembersAndTypeNames should mean that the new object has a fresh/empty list of instance members"); foreach (PSMemberInfo member in this.InstanceMembers) { if (member.IsHidden) @@ -1851,7 +1851,7 @@ internal static object GetNoteSettingValue(PSMemberSet settings, string noteName settings.ReplicateInstance(ownerObject); } - if (!(settings.Members[noteName] is PSNoteProperty note)) + if (settings.Members[noteName] is not PSNoteProperty note) { return defaultValue; } diff --git a/src/System.Management.Automation/engine/MshObjectTypeDescriptor.cs b/src/System.Management.Automation/engine/MshObjectTypeDescriptor.cs index 11955713c7e..113161748c9 100644 --- a/src/System.Management.Automation/engine/MshObjectTypeDescriptor.cs +++ b/src/System.Management.Automation/engine/MshObjectTypeDescriptor.cs @@ -219,7 +219,7 @@ private static PSObject GetComponentPSObject(object component) PSObject mshObj = component as PSObject; if (mshObj == null) { - if (!(component is PSObjectTypeDescriptor descriptor)) + if (component is not PSObjectTypeDescriptor descriptor) { throw PSTraceSource.NewArgumentException(nameof(component), ExtendedTypeSystem.InvalidComponent, "component", @@ -464,7 +464,7 @@ public override PropertyDescriptorCollection GetProperties(Attribute[] attribute /// <returns>True if the Instance property of <paramref name="obj"/> is equal to the current Instance; otherwise, false.</returns> public override bool Equals(object obj) { - if (!(obj is PSObjectTypeDescriptor other)) + if (obj is not PSObjectTypeDescriptor other) { return false; } diff --git a/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs b/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs index 0124b01332c..31ac506c86a 100644 --- a/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs +++ b/src/System.Management.Automation/engine/NativeCommandParameterBinder.cs @@ -161,7 +161,7 @@ internal string[] ArgumentList /// <param name="argument">The value used with parameter.</param> internal void AddToArgumentList(CommandParameterInternal parameter, string argument) { - if (parameter.ParameterNameSpecified && parameter.ParameterText.EndsWith(":")) + if (parameter.ParameterNameSpecified && parameter.ParameterText.EndsWith(':')) { if (argument != parameter.ParameterText) { @@ -406,12 +406,9 @@ private void PossiblyGlobArg(string arg, CommandParameterInternal parameter, boo } } #else - if (!usedQuotes && ExperimentalFeature.IsEnabled(ExperimentalFeature.PSNativeWindowsTildeExpansion)) + if (!usedQuotes && ExpandTilde(arg, parameter)) { - if (ExpandTilde(arg, parameter)) - { - argExpanded = true; - } + argExpanded = true; } #endif @@ -500,7 +497,7 @@ private static string GetEnumerableArgSeparator(ArrayLiteralAst arrayLiteralAst, afterPrev -= arrayExtent.StartOffset; beforeNext -= arrayExtent.StartOffset; - if (arrayText[afterPrev] == ',') + if (arrayText[afterPrev] == ',') { return ", "; } @@ -509,7 +506,7 @@ private static string GetEnumerableArgSeparator(ArrayLiteralAst arrayLiteralAst, { return " ,"; } - + return " , "; } diff --git a/src/System.Management.Automation/engine/NativeCommandProcessor.cs b/src/System.Management.Automation/engine/NativeCommandProcessor.cs index 714bf8f2d4f..145fe968fda 100644 --- a/src/System.Management.Automation/engine/NativeCommandProcessor.cs +++ b/src/System.Management.Automation/engine/NativeCommandProcessor.cs @@ -11,15 +11,17 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; +using System.Linq; using System.Management.Automation.Internal; -using System.Management.Automation.Runspaces; -using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Xml; +using Microsoft.PowerShell.Commands; using Microsoft.PowerShell.Telemetry; +using Microsoft.Win32; using Dbg = System.Management.Automation.Diagnostics; namespace System.Management.Automation @@ -194,27 +196,211 @@ internal NativeCommandExitException(string path, int exitCode, int processId, st /// </summary> internal class NativeCommandProcessor : CommandProcessorBase { - // This is the list of files which will trigger Legacy behavior if - // PSNativeCommandArgumentPassing is set to "Windows". - private static readonly IReadOnlySet<string> s_legacyFileExtensions = new HashSet<string>(StringComparer.OrdinalIgnoreCase) + /// <summary> + /// This is the list of files which will trigger Legacy behavior if 'PSNativeCommandArgumentPassing' is set to "Windows". + /// </summary> + private static readonly HashSet<string> s_legacyFileExtensions = new(StringComparer.OrdinalIgnoreCase) + { + ".js", + ".wsf", + ".cmd", + ".bat", + ".vbs", + }; + + /// <summary> + /// This is the list of native commands that have non-standard behavior with regard to argument passing. + /// We use Legacy argument parsing for them when 'PSNativeCommandArgumentPassing' is set to "Windows". + /// </summary> + private static readonly HashSet<string> s_legacyCommands = new(StringComparer.OrdinalIgnoreCase) + { + "cmd", + "cscript", + "find", + "sqlcmd", + "wscript", + }; + +#if !UNIX + /// <summary> + /// List of known package managers pulled from the registry. + /// </summary> + private static readonly HashSet<string> s_knownPackageManagers = GetPackageManagerListFromRegistry(); + + /// <summary> + /// Indicates whether the Path Update feature is enabled in a given session. + /// PowerShell sessions could reuse the same thread, so we cannot cache the value with a thread static variable. + /// </summary> + private static readonly ConditionalWeakTable<ExecutionContext, string> s_pathUpdateFeatureEnabled = new(); + + private readonly bool _isPackageManager; + private string _originalUserEnvPath; + private string _originalSystemEnvPath; + + /// <summary> + /// Gets the known package managers from the registry. + /// </summary> + private static HashSet<string> GetPackageManagerListFromRegistry() + { + // We only account for the first 8 package managers. This is the same behavior as in CMD. + const int MaxPackageManagerCount = 8; + const string RegKeyPath = @"Software\Microsoft\Command Processor\KnownPackageManagers"; + + string[] subKeyNames = null; + HashSet<string> retSet = null; + + try + { + using RegistryKey key = Registry.LocalMachine.OpenSubKey(RegKeyPath); + subKeyNames = key?.GetSubKeyNames(); + } + catch + { + return null; + } + + if (subKeyNames is { Length: > 0 }) + { + IEnumerable<string> names = subKeyNames.Length <= MaxPackageManagerCount + ? subKeyNames + : subKeyNames.Take(MaxPackageManagerCount); + + retSet = new(names, StringComparer.OrdinalIgnoreCase); + } + + return retSet; + } + + /// <summary> + /// Check if the given name is a known package manager from the registry list. + /// </summary> + private static bool IsKnownPackageManager(string name) { - ".js", - ".wsf", - ".cmd", - ".bat", - ".vbs", - }; - - // The following native commands have non-standard behavior with regard to argument passing, - // so we use Legacy argument parsing for them when PSNativeCommandArgumentPassing is set to Windows. - private static readonly IReadOnlySet<string> s_legacyCommands = new HashSet<string>(StringComparer.OrdinalIgnoreCase) + if (s_knownPackageManagers is null) + { + return false; + } + + if (s_knownPackageManagers.Contains(name)) + { + return true; + } + + int lastDotIndex = name.LastIndexOf('.'); + if (lastDotIndex > 0) + { + string nameWithoutExt = name[..lastDotIndex]; + if (s_knownPackageManagers.Contains(nameWithoutExt)) + { + return true; + } + } + + return false; + } + + /// <summary> + /// Check if the Path Update feature is enabled for the given session. + /// </summary> + private static bool IsPathUpdateFeatureEnabled(ExecutionContext context) { - "cmd", - "cscript", - "find", - "sqlcmd", - "wscript", - }; + // We check only once per session. + if (s_pathUpdateFeatureEnabled.TryGetValue(context, out string value)) + { + // The feature is enabled if the value is not null. + return value is { }; + } + + // Disable Path Update if 'EnvironmentProvider' is disabled in the current session, or the current session is restricted. + bool enabled = context.EngineSessionState.Providers.ContainsKey(EnvironmentProvider.ProviderName) + && !Utils.IsSessionRestricted(context); + + // - Use the static empty string instance to indicate that the feature is enabled. + // - Use the null value to indicate that the feature is disabled. + s_pathUpdateFeatureEnabled.TryAdd(context, enabled ? string.Empty : null); + return enabled; + } + + /// <summary> + /// Gets the added part of the new string compared to the old string. + /// </summary> + private static ReadOnlySpan<char> GetAddedPartOfString(string oldString, string newString) + { + if (oldString.Length >= newString.Length) + { + // Nothing added or something removed. + return ReadOnlySpan<char>.Empty; + } + + int index = newString.IndexOf(oldString); + if (index is -1) + { + // The new and old strings are drastically different. Stop trying in this case. + return ReadOnlySpan<char>.Empty; + } + + if (index > 0) + { + // Found the old string at non-zero offset, so something was prepended to the old string. + return newString.AsSpan(0, index); + } + else + { + // Found the old string at the beginning of the new string, so something was appended to the old string. + return newString.AsSpan(oldString.Length); + } + } + + /// <summary> + /// Update the process-scope environment variable Path based on the changes in the user-scope and system-scope Path. + /// </summary> + /// <param name="oldUserPath">The old value of the user-scope Path retrieved from registry.</param> + /// <param name="oldSystemPath">The old value of the system-scope Path retrieved from registry.</param> + private static void UpdateProcessEnvPath(string oldUserPath, string oldSystemPath) + { + string newUserEnvPath = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.User); + string newSystemEnvPath = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine); + string procEnvPath = Environment.GetEnvironmentVariable("Path"); + + ReadOnlySpan<char> userPathChange = GetAddedPartOfString(oldUserPath, newUserEnvPath).Trim(';'); + ReadOnlySpan<char> systemPathChange = GetAddedPartOfString(oldSystemPath, newSystemEnvPath).Trim(';'); + + // Add 2 to account for the path separators we may need to add. + int maxLength = procEnvPath.Length + userPathChange.Length + systemPathChange.Length + 2; + StringBuilder newPath = null; + + if (userPathChange.Length > 0) + { + CreateNewProcEnvPath(userPathChange); + } + + if (systemPathChange.Length > 0) + { + CreateNewProcEnvPath(systemPathChange); + } + + if (newPath is { Length: > 0 }) + { + // Update the process env Path. + Environment.SetEnvironmentVariable("Path", newPath.ToString()); + } + + // Helper method to create a new env Path string. + void CreateNewProcEnvPath(ReadOnlySpan<char> newChange) + { + newPath ??= new StringBuilder(procEnvPath, capacity: maxLength); + + if (newPath.Length is 0 || newPath[^1] is ';') + { + newPath.Append(newChange); + } + else + { + newPath.Append(';').Append(newChange); + } + } + } +#endif #region ctor/native command properties @@ -262,7 +448,11 @@ internal NativeCommandProcessor(ApplicationInfo applicationInfo, ExecutionContex // Create input writer for providing input to the process. _inputWriter = new ProcessInputWriter(Command); - _isTranscribing = this.Command.Context.EngineHostInterface.UI.IsTranscribing; + _isTranscribing = context.EngineHostInterface.UI.IsTranscribing; + +#if !UNIX + _isPackageManager = IsKnownPackageManager(_applicationInfo.Name) && IsPathUpdateFeatureEnabled(context); +#endif } /// <summary> @@ -418,7 +608,7 @@ internal override void ProcessRecord() /// <summary> /// Process object for the invoked application. /// </summary> - private System.Diagnostics.Process _nativeProcess; + private Process _nativeProcess; /// <summary> /// This is used for writing input to the process. @@ -560,6 +750,12 @@ private void InitNativeProcess() // must set UseShellExecute to false if we modify the environment block startInfo.UseShellExecute = false; } + + if (_isPackageManager) + { + _originalUserEnvPath = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.User); + _originalSystemEnvPath = Environment.GetEnvironmentVariable("Path", EnvironmentVariableTarget.Machine); + } #endif if (this.Command.Context.CurrentPipelineStopping) @@ -635,6 +831,7 @@ private void InitNativeProcess() bool useSpecialArgumentPassing = UseSpecialArgumentPassing(oldFileName); if (useSpecialArgumentPassing) { + // codeql[cs/microsoft/command-line-injection] - This is expected PowerShell behavior where user inputted paths are supported for the context of this method and the path portion of the argument is escaped. The user assumes trust for the file path specified on the user's system to start process for, and in the case of remoting, restricted remoting security guidelines should be used. startInfo.Arguments = "\"" + oldFileName + "\" " + startInfo.Arguments; } else @@ -658,6 +855,8 @@ private void InitNativeProcess() { startInfo.ArgumentList.RemoveAt(0); } + + // codeql[cs/microsoft/command-line-injection-shell-execution] - This is expected PowerShell behavior where user inputted paths are supported for the context of this method. The user assumes trust for the file path specified on the user's system to retrieve process info for, and in the case of remoting, restricted remoting security guidelines should be used. startInfo.FileName = oldFileName; } } @@ -898,6 +1097,13 @@ internal override void Complete() ConsumeAvailableNativeProcessOutput(blocking: true); _nativeProcess.WaitForExit(); +#if !UNIX + if (_isPackageManager) + { + UpdateProcessEnvPath(_originalUserEnvPath, _originalSystemEnvPath); + } +#endif + // Capture screen output if we are transcribing and running stand alone if (_isTranscribing && (s_supportScreenScrape == true) && _runStandAlone) { @@ -1402,6 +1608,7 @@ private ProcessStartInfo GetProcessStartInfo( { var startInfo = new ProcessStartInfo { + // codeql[cs/microsoft/command-line-injection-shell-execution] - This is expected PowerShell behavior where user inputted paths are supported for the context of this method. The user assumes trust for the file path specified on the user's system to retrieve process info for, and in the case of remoting, restricted remoting security guidelines should be used. FileName = this.Path }; @@ -1439,16 +1646,17 @@ private ProcessStartInfo GetProcessStartInfo( startInfo.UseShellExecute = false; startInfo.RedirectStandardInput = redirectInput; + Encoding outputEncoding = GetOutputEncoding(); if (redirectOutput) { startInfo.RedirectStandardOutput = true; - startInfo.StandardOutputEncoding = Console.OutputEncoding; + startInfo.StandardOutputEncoding = outputEncoding; } if (redirectError) { startInfo.RedirectStandardError = true; - startInfo.StandardErrorEncoding = Console.OutputEncoding; + startInfo.StandardErrorEncoding = outputEncoding; } } @@ -1471,6 +1679,7 @@ private ProcessStartInfo GetProcessStartInfo( { using (ParameterBinderBase.bindingTracer.TraceScope("BIND argument [{0}]", NativeParameterBinderController.Arguments)) { + // codeql[cs/microsoft/command-line-injection ] - This is intended PowerShell behavior as NativeParameterBinderController.Arguments is what the native parameter binder generates based on the user input when invoking the command and cannot be injected externally. startInfo.Arguments = NativeParameterBinderController.Arguments; } } @@ -1505,6 +1714,20 @@ private ProcessStartInfo GetProcessStartInfo( return startInfo; } +#nullable enable + /// <summary> + /// Gets the encoding to use for a process' output/error pipes. + /// </summary> + /// <returns>The encoding to use for the process output.</returns> + private Encoding GetOutputEncoding() + { + Encoding? applicationOutputEncoding = Context.GetVariableValue( + SpecialVariables.PSApplicationOutputEncodingVarPath) as Encoding; + + return applicationOutputEncoding ?? Console.OutputEncoding; + } +#nullable disable + /// <summary> /// Determine if we have a special file which will change the way native argument passing /// is done on Windows. We use legacy behavior for cmd.exe, .bat, .cmd files. @@ -1717,6 +1940,7 @@ private bool IsExecutable(string path) #region Minishell Interop private bool _isMiniShell = false; + /// <summary> /// Returns true if native command being invoked is mini-shell. /// </summary> @@ -2301,7 +2525,6 @@ internal static bool AllocateHiddenConsole() /// This remote instance of PowerShell can be in a separate process, /// appdomain or machine. /// </remarks> - [SuppressMessage("Microsoft.Usage", "CA2240:ImplementISerializableCorrectly")] public class RemoteException : RuntimeException { /// <summary> diff --git a/src/System.Management.Automation/engine/PSConfiguration.cs b/src/System.Management.Automation/engine/PSConfiguration.cs index e321423f768..419a4cae95f 100644 --- a/src/System.Management.Automation/engine/PSConfiguration.cs +++ b/src/System.Management.Automation/engine/PSConfiguration.cs @@ -89,7 +89,10 @@ private PowerShellConfig() // Note: This directory may or may not exist depending upon the execution scenario. // Writes will attempt to create the directory if it does not already exist. perUserConfigDirectory = Platform.ConfigDirectory; - perUserConfigFile = Path.Combine(perUserConfigDirectory, ConfigFileName); + if (!string.IsNullOrEmpty(perUserConfigDirectory)) + { + perUserConfigFile = Path.Combine(perUserConfigDirectory, ConfigFileName); + } emptyConfig = new JObject(); configRoots = new JObject[2]; @@ -387,6 +390,11 @@ internal PSKeyword GetLogKeywords() private T ReadValueFromFile<T>(ConfigScope scope, string key, T defaultValue = default) { string fileName = GetConfigFilePath(scope); + if (string.IsNullOrEmpty(fileName)) + { + return defaultValue; + } + JObject configData = configRoots[(int)scope]; if (configData == null) diff --git a/src/System.Management.Automation/engine/PSVersionInfo.cs b/src/System.Management.Automation/engine/PSVersionInfo.cs index 75f75cc112f..eef9819a568 100644 --- a/src/System.Management.Automation/engine/PSVersionInfo.cs +++ b/src/System.Management.Automation/engine/PSVersionInfo.cs @@ -795,7 +795,7 @@ public int CompareTo(object version) return 1; } - if (!(version is SemanticVersion v)) + if (version is not SemanticVersion v) { throw PSTraceSource.NewArgumentException(nameof(version)); } diff --git a/src/System.Management.Automation/engine/ProgressRecord.cs b/src/System.Management.Automation/engine/ProgressRecord.cs index 56d5518dcf8..b3139dc8e6e 100644 --- a/src/System.Management.Automation/engine/ProgressRecord.cs +++ b/src/System.Management.Automation/engine/ProgressRecord.cs @@ -293,7 +293,7 @@ internal ProgressRecord(ProgressRecord other) } /// <summary> - /// Overrides <see cref="System.Object.ToString"/> + /// Overrides <see cref="object.ToString"/> /// </summary> /// <returns> /// "parent = a id = b act = c stat = d cur = e pct = f sec = g type = h" where diff --git a/src/System.Management.Automation/engine/SessionStateContainer.cs b/src/System.Management.Automation/engine/SessionStateContainer.cs index e97d62c87d1..fa8884b92e8 100644 --- a/src/System.Management.Automation/engine/SessionStateContainer.cs +++ b/src/System.Management.Automation/engine/SessionStateContainer.cs @@ -1839,7 +1839,7 @@ private void ProcessPathItems( return; } - if (!(childNameObjects[index].BaseObject is string childName)) + if (childNameObjects[index].BaseObject is not string childName) { continue; } @@ -2584,7 +2584,7 @@ private void DoGetChildNamesManually( return; } - if (!(result.BaseObject is string name)) + if (result.BaseObject is not string name) { continue; } @@ -2632,7 +2632,7 @@ private void DoGetChildNamesManually( return; } - if (!(result.BaseObject is string name)) + if (result.BaseObject is not string name) { continue; } diff --git a/src/System.Management.Automation/engine/SessionStateDriveAPIs.cs b/src/System.Management.Automation/engine/SessionStateDriveAPIs.cs index 34c1ac50521..d01d4c63f5d 100644 --- a/src/System.Management.Automation/engine/SessionStateDriveAPIs.cs +++ b/src/System.Management.Automation/engine/SessionStateDriveAPIs.cs @@ -1434,4 +1434,3 @@ internal PSDriveInfo CurrentDrive } #pragma warning restore 56500 - diff --git a/src/System.Management.Automation/engine/SessionStateItem.cs b/src/System.Management.Automation/engine/SessionStateItem.cs index 3d5262bf6c0..173043515ba 100644 --- a/src/System.Management.Automation/engine/SessionStateItem.cs +++ b/src/System.Management.Automation/engine/SessionStateItem.cs @@ -1375,4 +1375,3 @@ private object InvokeDefaultActionDynamicParameters( } #pragma warning restore 56500 - diff --git a/src/System.Management.Automation/engine/SessionStateNavigation.cs b/src/System.Management.Automation/engine/SessionStateNavigation.cs index a1139ebc198..d2d27ec2d83 100644 --- a/src/System.Management.Automation/engine/SessionStateNavigation.cs +++ b/src/System.Management.Automation/engine/SessionStateNavigation.cs @@ -1748,4 +1748,3 @@ private object MoveItemDynamicParameters( } #pragma warning restore 56500 - diff --git a/src/System.Management.Automation/engine/SessionStateProperty.cs b/src/System.Management.Automation/engine/SessionStateProperty.cs index d4568d2ebb3..9f621fa2f7a 100644 --- a/src/System.Management.Automation/engine/SessionStateProperty.cs +++ b/src/System.Management.Automation/engine/SessionStateProperty.cs @@ -1116,4 +1116,3 @@ private object ClearPropertyDynamicParameters( } #pragma warning restore 56500 - diff --git a/src/System.Management.Automation/engine/SessionStateProviderAPIs.cs b/src/System.Management.Automation/engine/SessionStateProviderAPIs.cs index 0c94c9ea059..3ad2ed99e31 100644 --- a/src/System.Management.Automation/engine/SessionStateProviderAPIs.cs +++ b/src/System.Management.Automation/engine/SessionStateProviderAPIs.cs @@ -350,7 +350,7 @@ internal DriveCmdletProvider GetDriveProviderInstance(string providerId) throw PSTraceSource.NewArgumentNullException(nameof(providerId)); } - if (!(GetProviderInstance(providerId) is DriveCmdletProvider driveCmdletProvider)) + if (GetProviderInstance(providerId) is not DriveCmdletProvider driveCmdletProvider) { throw PSTraceSource.NewNotSupportedException(SessionStateStrings.DriveCmdletProvider_NotSupported); @@ -382,7 +382,7 @@ internal DriveCmdletProvider GetDriveProviderInstance(ProviderInfo provider) throw PSTraceSource.NewArgumentNullException(nameof(provider)); } - if (!(GetProviderInstance(provider) is DriveCmdletProvider driveCmdletProvider)) + if (GetProviderInstance(provider) is not DriveCmdletProvider driveCmdletProvider) { throw PSTraceSource.NewNotSupportedException(SessionStateStrings.DriveCmdletProvider_NotSupported); @@ -414,7 +414,7 @@ private static DriveCmdletProvider GetDriveProviderInstance(CmdletProvider provi throw PSTraceSource.NewArgumentNullException(nameof(providerInstance)); } - if (!(providerInstance is DriveCmdletProvider driveCmdletProvider)) + if (providerInstance is not DriveCmdletProvider driveCmdletProvider) { throw PSTraceSource.NewNotSupportedException(SessionStateStrings.DriveCmdletProvider_NotSupported); @@ -449,7 +449,7 @@ internal ItemCmdletProvider GetItemProviderInstance(string providerId) throw PSTraceSource.NewArgumentNullException(nameof(providerId)); } - if (!(GetProviderInstance(providerId) is ItemCmdletProvider itemCmdletProvider)) + if (GetProviderInstance(providerId) is not ItemCmdletProvider itemCmdletProvider) { throw PSTraceSource.NewNotSupportedException(SessionStateStrings.ItemCmdletProvider_NotSupported); @@ -481,7 +481,7 @@ internal ItemCmdletProvider GetItemProviderInstance(ProviderInfo provider) throw PSTraceSource.NewArgumentNullException(nameof(provider)); } - if (!(GetProviderInstance(provider) is ItemCmdletProvider itemCmdletProvider)) + if (GetProviderInstance(provider) is not ItemCmdletProvider itemCmdletProvider) { throw PSTraceSource.NewNotSupportedException(SessionStateStrings.ItemCmdletProvider_NotSupported); @@ -513,7 +513,7 @@ private static ItemCmdletProvider GetItemProviderInstance(CmdletProvider provide throw PSTraceSource.NewArgumentNullException(nameof(providerInstance)); } - if (!(providerInstance is ItemCmdletProvider itemCmdletProvider)) + if (providerInstance is not ItemCmdletProvider itemCmdletProvider) { throw PSTraceSource.NewNotSupportedException(SessionStateStrings.ItemCmdletProvider_NotSupported); @@ -548,7 +548,7 @@ internal ContainerCmdletProvider GetContainerProviderInstance(string providerId) throw PSTraceSource.NewArgumentNullException(nameof(providerId)); } - if (!(GetProviderInstance(providerId) is ContainerCmdletProvider containerCmdletProvider)) + if (GetProviderInstance(providerId) is not ContainerCmdletProvider containerCmdletProvider) { throw PSTraceSource.NewNotSupportedException(SessionStateStrings.ContainerCmdletProvider_NotSupported); @@ -580,7 +580,7 @@ internal ContainerCmdletProvider GetContainerProviderInstance(ProviderInfo provi throw PSTraceSource.NewArgumentNullException(nameof(provider)); } - if (!(GetProviderInstance(provider) is ContainerCmdletProvider containerCmdletProvider)) + if (GetProviderInstance(provider) is not ContainerCmdletProvider containerCmdletProvider) { throw PSTraceSource.NewNotSupportedException(SessionStateStrings.ContainerCmdletProvider_NotSupported); @@ -612,7 +612,7 @@ private static ContainerCmdletProvider GetContainerProviderInstance(CmdletProvid throw PSTraceSource.NewArgumentNullException(nameof(providerInstance)); } - if (!(providerInstance is ContainerCmdletProvider containerCmdletProvider)) + if (providerInstance is not ContainerCmdletProvider containerCmdletProvider) { throw PSTraceSource.NewNotSupportedException(SessionStateStrings.ContainerCmdletProvider_NotSupported); @@ -644,7 +644,7 @@ internal NavigationCmdletProvider GetNavigationProviderInstance(ProviderInfo pro throw PSTraceSource.NewArgumentNullException(nameof(provider)); } - if (!(GetProviderInstance(provider) is NavigationCmdletProvider navigationCmdletProvider)) + if (GetProviderInstance(provider) is not NavigationCmdletProvider navigationCmdletProvider) { throw PSTraceSource.NewNotSupportedException(SessionStateStrings.NavigationCmdletProvider_NotSupported); diff --git a/src/System.Management.Automation/engine/SessionStateSecurityDescriptorInterface.cs b/src/System.Management.Automation/engine/SessionStateSecurityDescriptorInterface.cs index 16ebe0fc3b5..08635c7efe1 100644 --- a/src/System.Management.Automation/engine/SessionStateSecurityDescriptorInterface.cs +++ b/src/System.Management.Automation/engine/SessionStateSecurityDescriptorInterface.cs @@ -35,7 +35,7 @@ internal static ISecurityDescriptorCmdletProvider GetPermissionProviderInstance( throw PSTraceSource.NewArgumentNullException(nameof(providerInstance)); } - if (!(providerInstance is ISecurityDescriptorCmdletProvider permissionCmdletProvider)) + if (providerInstance is not ISecurityDescriptorCmdletProvider permissionCmdletProvider) { throw PSTraceSource.NewNotSupportedException( diff --git a/src/System.Management.Automation/engine/SpecialVariables.cs b/src/System.Management.Automation/engine/SpecialVariables.cs index 7563f89a919..51419a0d5c2 100644 --- a/src/System.Management.Automation/engine/SpecialVariables.cs +++ b/src/System.Management.Automation/engine/SpecialVariables.cs @@ -41,6 +41,10 @@ internal static class SpecialVariables internal static readonly VariablePath OutputEncodingVarPath = new VariablePath(OutputEncoding); + internal const string PSApplicationOutputEncoding = nameof(PSApplicationOutputEncoding); + + internal static readonly VariablePath PSApplicationOutputEncodingVarPath = new VariablePath(PSApplicationOutputEncoding); + internal const string VerboseHelpErrors = "VerboseHelpErrors"; internal static readonly VariablePath VerboseHelpErrorsVarPath = new VariablePath(VerboseHelpErrors); @@ -388,6 +392,7 @@ internal static class SpecialVariables SpecialVariables.NestedPromptLevel, SpecialVariables.pwd, SpecialVariables.Matches, + SpecialVariables.PSApplicationOutputEncoding, }, StringComparer.OrdinalIgnoreCase ); diff --git a/src/System.Management.Automation/engine/Subsystem/Commands/GetPSSubsystemCommand.cs b/src/System.Management.Automation/engine/Subsystem/Commands/GetPSSubsystemCommand.cs index 1ad79404f51..df828c724a2 100644 --- a/src/System.Management.Automation/engine/Subsystem/Commands/GetPSSubsystemCommand.cs +++ b/src/System.Management.Automation/engine/Subsystem/Commands/GetPSSubsystemCommand.cs @@ -8,7 +8,6 @@ namespace System.Management.Automation.Subsystem /// <summary> /// Implementation of 'Get-PSSubsystem' cmdlet. /// </summary> - [Experimental("PSSubsystemPluginModel", ExperimentAction.Show)] [Cmdlet(VerbsCommon.Get, "PSSubsystem", DefaultParameterSetName = AllSet)] [OutputType(typeof(SubsystemInfo))] public sealed class GetPSSubsystemCommand : PSCmdlet diff --git a/src/System.Management.Automation/engine/Subsystem/FeedbackSubsystem/FeedbackHub.cs b/src/System.Management.Automation/engine/Subsystem/FeedbackSubsystem/FeedbackHub.cs index 588cf086d42..b1aa781fea7 100644 --- a/src/System.Management.Automation/engine/Subsystem/FeedbackSubsystem/FeedbackHub.cs +++ b/src/System.Management.Automation/engine/Subsystem/FeedbackSubsystem/FeedbackHub.cs @@ -52,7 +52,7 @@ public static class FeedbackHub /// </summary> public static List<FeedbackResult>? GetFeedback(Runspace runspace) { - return GetFeedback(runspace, millisecondsTimeout: 300); + return GetFeedback(runspace, millisecondsTimeout: 1000); } /// <summary> diff --git a/src/System.Management.Automation/engine/TransactedString.cs b/src/System.Management.Automation/engine/TransactedString.cs index 22a0afe9a9a..05eab93d198 100644 --- a/src/System.Management.Automation/engine/TransactedString.cs +++ b/src/System.Management.Automation/engine/TransactedString.cs @@ -195,4 +195,3 @@ private void ValidateTransactionOrEnlist() } } } - diff --git a/src/System.Management.Automation/engine/TransactionManager.cs b/src/System.Management.Automation/engine/TransactionManager.cs index 2add6f288f5..e77ffebedb4 100644 --- a/src/System.Management.Automation/engine/TransactionManager.cs +++ b/src/System.Management.Automation/engine/TransactionManager.cs @@ -706,4 +706,3 @@ public void Dispose(bool disposing) } } } - diff --git a/src/System.Management.Automation/engine/TypeTable.cs b/src/System.Management.Automation/engine/TypeTable.cs index bffeb9e0ceb..a0eead95e23 100644 --- a/src/System.Management.Automation/engine/TypeTable.cs +++ b/src/System.Management.Automation/engine/TypeTable.cs @@ -3922,7 +3922,7 @@ internal Collection<string> GetSpecificProperties(ConsolidatedString types) } PSMemberSet settings = typeMembers[PSStandardMembers] as PSMemberSet; - if (!(settings?.Members[PropertySerializationSet] is PSPropertySet typeProperties)) + if (settings?.Members[PropertySerializationSet] is not PSPropertySet typeProperties) { continue; } diff --git a/src/System.Management.Automation/engine/TypeTable_Types_Ps1Xml.cs b/src/System.Management.Automation/engine/TypeTable_Types_Ps1Xml.cs index c1a134cdfbf..26e3621c240 100644 --- a/src/System.Management.Automation/engine/TypeTable_Types_Ps1Xml.cs +++ b/src/System.Management.Automation/engine/TypeTable_Types_Ps1Xml.cs @@ -565,9 +565,7 @@ private void Process_Types_Ps1Xml(string filePath, ConcurrentBag<string> errors) typeName, new PSScriptProperty( @"DisplayName", - GetScriptBlock(@"if ($this.Name.IndexOf('-') -lt 0) - { - if ($null -ne $this.ResolvedCommand) + GetScriptBlock(@"if ($null -ne $this.ResolvedCommand) { $this.Name + "" -> "" + $this.ResolvedCommand.Name } @@ -575,11 +573,7 @@ private void Process_Types_Ps1Xml(string filePath, ConcurrentBag<string> errors) { $this.Name + "" -> "" + $this.Definition } - } - else - { - $this.Name - }"), + "), setterScript: null, shouldCloneOnAccess: true), typeMembers, diff --git a/src/System.Management.Automation/engine/Utils.cs b/src/System.Management.Automation/engine/Utils.cs index 2a2091af31a..0de9fe0d5cc 100644 --- a/src/System.Management.Automation/engine/Utils.cs +++ b/src/System.Management.Automation/engine/Utils.cs @@ -24,7 +24,6 @@ using System.Threading; using Microsoft.PowerShell.Commands; using Microsoft.Win32; -using Microsoft.Win32.SafeHandles; using TypeTable = System.Management.Automation.Runspaces.TypeTable; @@ -1539,14 +1538,80 @@ internal static string DisplayHumanReadableFileSize(long bytes) /// <returns>True if the session is restricted.</returns> internal static bool IsSessionRestricted(ExecutionContext context) { - CmdletInfo cmdletInfo = context.SessionState.InvokeCommand.GetCmdlet("Microsoft.PowerShell.Core\\Import-Module"); - // if import-module is visible, then the session is not restricted, - // because the user can load arbitrary code. - if (cmdletInfo != null && cmdletInfo.Visibility == SessionStateEntryVisibility.Public) + CmdletInfo cmdletInfo = context.SessionState.InvokeCommand.GetCmdlet("Microsoft.PowerShell.Core\\Import-Module"); + // if import-module is visible, then the session is not restricted, + // because the user can load arbitrary code. + if (cmdletInfo != null && cmdletInfo.Visibility == SessionStateEntryVisibility.Public) + { + return false; + } + return true; + } + + /// <summary> + /// Determine whether the environment variable is set and how. + /// </summary> + /// <param name="name">The name of the environment variable.</param> + /// <param name="defaultValue">If the environment variable is not set, use this as the default value.</param> + /// <returns>A boolean representing the value of the environment variable.</returns> + internal static bool GetEnvironmentVariableAsBool(string name, bool defaultValue) + { + var str = Environment.GetEnvironmentVariable(name); + if (string.IsNullOrEmpty(str)) + { + return defaultValue; + } + + var boolStr = str.AsSpan(); + + if (boolStr.Length == 1) + { + if (boolStr[0] == '1') + { + return true; + } + + if (boolStr[0] == '0') { - return false; + return false; } + } + + if (boolStr.Length == 3 && + (boolStr[0] == 'y' || boolStr[0] == 'Y') && + (boolStr[1] == 'e' || boolStr[1] == 'E') && + (boolStr[2] == 's' || boolStr[2] == 'S')) + { return true; + } + + if (boolStr.Length == 2 && + (boolStr[0] == 'n' || boolStr[0] == 'N') && + (boolStr[1] == 'o' || boolStr[1] == 'O')) + { + return false; + } + + if (boolStr.Length == 4 && + (boolStr[0] == 't' || boolStr[0] == 'T') && + (boolStr[1] == 'r' || boolStr[1] == 'R') && + (boolStr[2] == 'u' || boolStr[2] == 'U') && + (boolStr[3] == 'e' || boolStr[3] == 'E')) + { + return true; + } + + if (boolStr.Length == 5 && + (boolStr[0] == 'f' || boolStr[0] == 'F') && + (boolStr[1] == 'a' || boolStr[1] == 'A') && + (boolStr[2] == 'l' || boolStr[2] == 'L') && + (boolStr[3] == 's' || boolStr[3] == 'S') && + (boolStr[4] == 'e' || boolStr[4] == 'E')) + { + return false; + } + + return defaultValue; } } } diff --git a/src/System.Management.Automation/engine/debugger/debugger.cs b/src/System.Management.Automation/engine/debugger/debugger.cs index bdad4960ac0..985d90ab3ec 100644 --- a/src/System.Management.Automation/engine/debugger/debugger.cs +++ b/src/System.Management.Automation/engine/debugger/debugger.cs @@ -2084,8 +2084,8 @@ private void SetPendingBreakpoints(FunctionContext functionContext) else { tuple.Item1.Add(breakpoint.SequencePointIndex, new List<LineBreakpoint> { breakpoint }); - } - + } + // We need to keep track of any breakpoints that are bound in each script because they may // need to be rebound if the script changes. var boundBreakpoints = _boundBreakpoints[currentScriptFile].Item2; @@ -2099,7 +2099,7 @@ private void SetPendingBreakpoints(FunctionContext functionContext) } // Here could check if all breakpoints for the current functionContext were bound, but because there is no atomic - // api for conditional removal we either need to lock, or do some trickery that has possibility of race conditions. + // api for conditional removal we either need to lock, or do some trickery that has possibility of race conditions. // Instead we keep the item in the dictionary with 0 breakpoint count. This should not be a big issue, // because it is single entry per file that had breakpoints, so there won't be thousands of files in a session. } @@ -2368,7 +2368,7 @@ public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataC // // Otherwise let root script debugger handle it. // - if (!(_context.CurrentRunspace is LocalRunspace localRunspace)) + if (_context.CurrentRunspace is not LocalRunspace localRunspace) { throw new PSInvalidOperationException( DebuggerStrings.CannotProcessDebuggerCommandNotStopped, @@ -3829,7 +3829,7 @@ private void HandleMonitorRunningRSDebuggerStop(object sender, DebuggerStopEvent } // Get nested debugger runspace info. - if (!(senderDebugger is NestedRunspaceDebugger nestedDebugger)) { return; } + if (senderDebugger is not NestedRunspaceDebugger nestedDebugger) { return; } PSMonitorRunspaceType runspaceType = nestedDebugger.RunspaceType; @@ -4686,7 +4686,7 @@ protected override void HandleDebuggerStop(object sender, DebuggerStopEventArgs private object DrainAndBlockRemoteOutput() { // We do this only for remote runspaces. - if (!(_runspace is RemoteRunspace remoteRunspace)) { return null; } + if (_runspace is not RemoteRunspace remoteRunspace) { return null; } var runningPowerShell = remoteRunspace.GetCurrentBasePowerShell(); if (runningPowerShell != null) diff --git a/src/System.Management.Automation/engine/hostifaces/Connection.cs b/src/System.Management.Automation/engine/hostifaces/Connection.cs index 3f5911d1c62..ccb918c7dd9 100644 --- a/src/System.Management.Automation/engine/hostifaces/Connection.cs +++ b/src/System.Management.Automation/engine/hostifaces/Connection.cs @@ -761,6 +761,12 @@ public string Name /// Gets the Runspace Id. /// </summary> public int Id { get; } + + /// <summary> + /// Gets and sets a boolean indicating whether the runspace has a + /// debugger attached with <c>Debug-Runspace</c>. + /// </summary> + public bool IsRemoteDebuggerAttached { get; internal set; } /// <summary> /// Returns protocol version that the remote server uses for PS remoting. diff --git a/src/System.Management.Automation/engine/hostifaces/FieldDescription.cs b/src/System.Management.Automation/engine/hostifaces/FieldDescription.cs index 541b1607df0..3f47fedb55e 100644 --- a/src/System.Management.Automation/engine/hostifaces/FieldDescription.cs +++ b/src/System.Management.Automation/engine/hostifaces/FieldDescription.cs @@ -89,7 +89,7 @@ public string Name /// </value> /// <remarks> /// If not already set by a call to <see cref="System.Management.Automation.Host.FieldDescription.SetParameterType"/>, - /// <see cref="System.String"/> will be used as the type. + /// <see cref="string"/> will be used as the type. /// <!--The value of ParameterTypeName is the string value returned. /// by System.Type.Name.--> /// </remarks> @@ -115,7 +115,7 @@ public string Name /// </summary> /// <remarks> /// If not already set by a call to <see cref="System.Management.Automation.Host.FieldDescription.SetParameterType"/>, - /// <see cref="System.String"/> will be used as the type. + /// <see cref="string"/> will be used as the type. /// <!--The value of ParameterTypeName is the string value returned. /// by System.Type.Name.--> /// </remarks> @@ -144,7 +144,7 @@ public string Name /// to load the containing assembly to access the type information. AssemblyName is used for this purpose. /// /// If not already set by a call to <see cref="System.Management.Automation.Host.FieldDescription.SetParameterType"/>, - /// <see cref="System.String"/> will be used as the type. + /// <see cref="string"/> will be used as the type. /// </remarks> public string diff --git a/src/System.Management.Automation/engine/hostifaces/History.cs b/src/System.Management.Automation/engine/hostifaces/History.cs index 3b503f2fade..00a090d4b22 100644 --- a/src/System.Management.Automation/engine/hostifaces/History.cs +++ b/src/System.Management.Automation/engine/hostifaces/History.cs @@ -1436,7 +1436,7 @@ void ProcessRecord() } // Read CommandLine property - if (!(GetPropertyValue(mshObject, "CommandLine") is string commandLine)) + if (GetPropertyValue(mshObject, "CommandLine") is not string commandLine) { break; } diff --git a/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs b/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs index 003625791b1..8e5d30be71f 100644 --- a/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs +++ b/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs @@ -1,10 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Management.Automation.Host; using System.Management.Automation.Internal; @@ -12,26 +10,10 @@ using System.Management.Automation.Subsystem.Feedback; using System.Runtime.InteropServices; using System.Text; -using System.Text.RegularExpressions; - -using Microsoft.PowerShell.Commands; using Microsoft.PowerShell.Commands.Internal.Format; namespace System.Management.Automation { - internal enum SuggestionMatchType - { - /// <summary>Match on a command.</summary> - Command = 0, - /// <summary>Match based on exception message.</summary> - Error = 1, - /// <summary>Match by running a script block.</summary> - Dynamic = 2, - - /// <summary>Match by fully qualified ErrorId.</summary> - ErrorId = 3 - } - #region Public HostUtilities Class /// <summary> @@ -43,31 +25,6 @@ public static class HostUtilities private static readonly char s_actionIndicator = HostSupportUnicode() ? '\u27a4' : '>'; - private static readonly string s_checkForCommandInCurrentDirectoryScript = @" - [System.Diagnostics.DebuggerHidden()] - param() - - $foundSuggestion = $false - - if($lastError -and - ($lastError.Exception -is ""System.Management.Automation.CommandNotFoundException"")) - { - $escapedCommand = [System.Management.Automation.WildcardPattern]::Escape($lastError.TargetObject) - $foundSuggestion = @(Get-Command ($ExecutionContext.SessionState.Path.Combine(""."", $escapedCommand)) -ErrorAction Ignore).Count -gt 0 - } - - $foundSuggestion - "; - - private static readonly string s_createCommandExistsInCurrentDirectoryScript = @" - [System.Diagnostics.DebuggerHidden()] - param([string] $formatString) - - $formatString -f $lastError.TargetObject,"".\$($lastError.TargetObject)"" - "; - - private static readonly List<Hashtable> s_suggestions = InitializeSuggestions(); - private static bool HostSupportUnicode() { // Reference: https://github.com/zkat/supports-unicode/blob/main/src/lib.rs @@ -87,23 +44,6 @@ private static bool HostSupportUnicode() return ctype.EndsWith("UTF8") || ctype.EndsWith("UTF-8"); } - private static List<Hashtable> InitializeSuggestions() - { - var suggestions = new List<Hashtable>() - { - NewSuggestion( - id: 3, - category: "General", - matchType: SuggestionMatchType.Dynamic, - rule: ScriptBlock.CreateDelayParsedScriptBlock(s_checkForCommandInCurrentDirectoryScript, isProductCode: true), - suggestion: ScriptBlock.CreateDelayParsedScriptBlock(s_createCommandExistsInCurrentDirectoryScript, isProductCode: true), - suggestionArgs: new object[] { SuggestionStrings.Suggestion_CommandExistsInCurrentDirectory_Legacy }, - enabled: true) - }; - - return suggestions; - } - #region GetProfileCommands /// <summary> /// Gets a PSObject whose base object is currentUserCurrentHost and with notes for the other 4 parameters. @@ -208,10 +148,11 @@ internal static string GetFullProfileFileName(string shellId, bool forCurrentUse else { basePath = GetAllUsersFolderPath(shellId); - if (string.IsNullOrEmpty(basePath)) - { - return string.Empty; - } + } + + if (string.IsNullOrEmpty(basePath)) + { + return string.Empty; } string profileName = useTestProfile ? "profile_test.ps1" : "profile.ps1"; @@ -282,303 +223,6 @@ internal static string GetMaxLines(string source, int maxLines) return returnValue.ToString(); } - internal static List<string> GetSuggestion(Runspace runspace) - { - if (!(runspace is LocalRunspace localRunspace)) - { - return new List<string>(); - } - - // Get the last value of $? - bool questionMarkVariableValue = localRunspace.ExecutionContext.QuestionMarkVariableValue; - - // Get the last history item - History history = localRunspace.History; - HistoryInfo[] entries = history.GetEntries(-1, 1, true); - - if (entries.Length == 0) - return new List<string>(); - - HistoryInfo lastHistory = entries[0]; - - // Get the last error - ArrayList errorList = (ArrayList)localRunspace.GetExecutionContext.DollarErrorVariable; - object lastError = null; - - if (errorList.Count > 0) - { - lastError = errorList[0] as Exception; - ErrorRecord lastErrorRecord = null; - - // The error was an actual ErrorRecord - if (lastError == null) - { - lastErrorRecord = errorList[0] as ErrorRecord; - } - else if (lastError is RuntimeException) - { - lastErrorRecord = ((RuntimeException)lastError).ErrorRecord; - } - - // If we got information about the error invocation, - // we can be more careful with the errors we pass along - if ((lastErrorRecord != null) && (lastErrorRecord.InvocationInfo != null)) - { - if (lastErrorRecord.InvocationInfo.HistoryId == lastHistory.Id) - lastError = lastErrorRecord; - else - lastError = null; - } - } - - Runspace oldDefault = null; - bool changedDefault = false; - if (Runspace.DefaultRunspace != runspace) - { - oldDefault = Runspace.DefaultRunspace; - changedDefault = true; - Runspace.DefaultRunspace = runspace; - } - - List<string> suggestions = null; - - try - { - suggestions = GetSuggestion(lastHistory, lastError, errorList); - } - finally - { - if (changedDefault) - { - Runspace.DefaultRunspace = oldDefault; - } - } - - // Restore $? - localRunspace.ExecutionContext.QuestionMarkVariableValue = questionMarkVariableValue; - return suggestions; - } - - [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")] - internal static List<string> GetSuggestion(HistoryInfo lastHistory, object lastError, ArrayList errorList) - { - var returnSuggestions = new List<string>(); - - PSModuleInfo invocationModule = new PSModuleInfo(true); - invocationModule.SessionState.PSVariable.Set("lastHistory", lastHistory); - invocationModule.SessionState.PSVariable.Set("lastError", lastError); - - int initialErrorCount = 0; - - // Go through all of the suggestions - foreach (Hashtable suggestion in s_suggestions) - { - initialErrorCount = errorList.Count; - - // Make sure the rule is enabled - if (!LanguagePrimitives.IsTrue(suggestion["Enabled"])) - continue; - - SuggestionMatchType matchType = (SuggestionMatchType)LanguagePrimitives.ConvertTo( - suggestion["MatchType"], - typeof(SuggestionMatchType), - CultureInfo.InvariantCulture); - - // If this is a dynamic match, evaluate the ScriptBlock - if (matchType == SuggestionMatchType.Dynamic) - { - object result = null; - - ScriptBlock evaluator = suggestion["Rule"] as ScriptBlock; - if (evaluator == null) - { - suggestion["Enabled"] = false; - - throw new ArgumentException( - SuggestionStrings.RuleMustBeScriptBlock, "Rule"); - } - - try - { - result = invocationModule.Invoke(evaluator, null); - } - catch (Exception) - { - // Catch-all OK. This is a third-party call-out. - suggestion["Enabled"] = false; - continue; - } - - // If it returned results, evaluate its suggestion - if (LanguagePrimitives.IsTrue(result)) - { - string suggestionText = GetSuggestionText(suggestion["Suggestion"], (object[])suggestion["SuggestionArgs"], invocationModule); - - if (!string.IsNullOrEmpty(suggestionText)) - { - string returnString = string.Format( - CultureInfo.CurrentCulture, - "Suggestion [{0},{1}]: {2}", - (int)suggestion["Id"], - (string)suggestion["Category"], - suggestionText); - - returnSuggestions.Add(returnString); - } - } - } - else - { - string matchText = string.Empty; - - // Otherwise, this is a Regex match against the - // command or error - if (matchType == SuggestionMatchType.Command) - { - matchText = lastHistory.CommandLine; - } - else if (matchType == SuggestionMatchType.Error) - { - if (lastError != null) - { - Exception lastException = lastError as Exception; - if (lastException != null) - { - matchText = lastException.Message; - } - else - { - matchText = lastError.ToString(); - } - } - } - else if (matchType == SuggestionMatchType.ErrorId) - { - if (lastError != null && lastError is ErrorRecord errorRecord) - { - matchText = errorRecord.FullyQualifiedErrorId; - } - } - else - { - suggestion["Enabled"] = false; - - throw new ArgumentException( - SuggestionStrings.InvalidMatchType, - "MatchType"); - } - - // If the text matches, evaluate the suggestion - if (Regex.IsMatch(matchText, (string)suggestion["Rule"], RegexOptions.IgnoreCase)) - { - string suggestionText = GetSuggestionText(suggestion["Suggestion"], (object[])suggestion["SuggestionArgs"], invocationModule); - - if (!string.IsNullOrEmpty(suggestionText)) - { - string returnString = string.Format( - CultureInfo.CurrentCulture, - "Suggestion [{0},{1}]: {2}", - (int)suggestion["Id"], - (string)suggestion["Category"], - suggestionText); - - returnSuggestions.Add(returnString); - } - } - } - - // If the rule generated an error, disable it - if (errorList.Count != initialErrorCount) - { - suggestion["Enabled"] = false; - } - } - - return returnSuggestions; - } - - /// <summary> - /// Create suggestion with string rule and scriptblock suggestion. - /// </summary> - /// <param name="id">Identifier for the suggestion.</param> - /// <param name="category">Category for the suggestion.</param> - /// <param name="matchType">Suggestion match type.</param> - /// <param name="rule">Rule to match.</param> - /// <param name="suggestion">Scriptblock to run that returns the suggestion.</param> - /// <param name="suggestionArgs">Arguments to pass to suggestion scriptblock.</param> - /// <param name="enabled">True if the suggestion is enabled.</param> - /// <returns>Hashtable representing the suggestion.</returns> - private static Hashtable NewSuggestion(int id, string category, SuggestionMatchType matchType, string rule, ScriptBlock suggestion, object[] suggestionArgs, bool enabled) - { - Hashtable result = new Hashtable(StringComparer.CurrentCultureIgnoreCase); - - result["Id"] = id; - result["Category"] = category; - result["MatchType"] = matchType; - result["Rule"] = rule; - result["Suggestion"] = suggestion; - result["SuggestionArgs"] = suggestionArgs; - result["Enabled"] = enabled; - - return result; - } - - /// <summary> - /// Create suggestion with scriptblock rule and suggestion. - /// </summary> - private static Hashtable NewSuggestion(int id, string category, SuggestionMatchType matchType, ScriptBlock rule, ScriptBlock suggestion, bool enabled) - { - Hashtable result = new Hashtable(StringComparer.CurrentCultureIgnoreCase); - - result["Id"] = id; - result["Category"] = category; - result["MatchType"] = matchType; - result["Rule"] = rule; - result["Suggestion"] = suggestion; - result["Enabled"] = enabled; - - return result; - } - - /// <summary> - /// Create suggestion with scriptblock rule and scriptblock suggestion with arguments. - /// </summary> - private static Hashtable NewSuggestion(int id, string category, SuggestionMatchType matchType, ScriptBlock rule, ScriptBlock suggestion, object[] suggestionArgs, bool enabled) - { - Hashtable result = NewSuggestion(id, category, matchType, rule, suggestion, enabled); - result.Add("SuggestionArgs", suggestionArgs); - - return result; - } - - /// <summary> - /// Get suggestion text from suggestion scriptblock with arguments. - /// </summary> - private static string GetSuggestionText(object suggestion, object[] suggestionArgs, PSModuleInfo invocationModule) - { - if (suggestion is ScriptBlock) - { - ScriptBlock suggestionScript = (ScriptBlock)suggestion; - - object result = null; - try - { - result = invocationModule.Invoke(suggestionScript, suggestionArgs); - } - catch (Exception) - { - // Catch-all OK. This is a third-party call-out. - return string.Empty; - } - - return (string)LanguagePrimitives.ConvertTo(result, typeof(string), CultureInfo.CurrentCulture); - } - else - { - return (string)LanguagePrimitives.ConvertTo(suggestion, typeof(string), CultureInfo.CurrentCulture); - } - } - /// <summary> /// Returns the prompt used in remote sessions: "[machine]: basePrompt" /// </summary> diff --git a/src/System.Management.Automation/engine/hostifaces/InternalHost.cs b/src/System.Management.Automation/engine/hostifaces/InternalHost.cs index 450941b4b63..c60bff90303 100644 --- a/src/System.Management.Automation/engine/hostifaces/InternalHost.cs +++ b/src/System.Management.Automation/engine/hostifaces/InternalHost.cs @@ -448,7 +448,7 @@ public override void NotifyEndApplication() /// </summary> private IHostSupportsInteractiveSession GetIHostSupportsInteractiveSession() { - if (!(_externalHostRef.Value is IHostSupportsInteractiveSession host)) + if (_externalHostRef.Value is not IHostSupportsInteractiveSession host) { throw new PSNotImplementedException(); } diff --git a/src/System.Management.Automation/engine/hostifaces/ListModifier.cs b/src/System.Management.Automation/engine/hostifaces/ListModifier.cs index db089b68333..aab1cb2e777 100644 --- a/src/System.Management.Automation/engine/hostifaces/ListModifier.cs +++ b/src/System.Management.Automation/engine/hostifaces/ListModifier.cs @@ -216,7 +216,7 @@ public void ApplyTo(object collectionToUpdate) collectionToUpdate = PSObject.Base(collectionToUpdate); - if (!(collectionToUpdate is IList list)) + if (collectionToUpdate is not IList list) { throw PSTraceSource.NewInvalidOperationException(PSListModifierStrings.UpdateFailed); } diff --git a/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs b/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs index 5dcc43ce2a7..822dc552204 100644 --- a/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs +++ b/src/System.Management.Automation/engine/hostifaces/LocalConnection.cs @@ -1557,11 +1557,11 @@ public PSDataCollection<ErrorRecord> ErrorRecords /// </summary> /// <param name="info">Serialization information.</param> /// <param name="context">Streaming context.</param> - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected RunspaceOpenModuleLoadException(SerializationInfo info, StreamingContext context) { throw new NotSupportedException(); - } + } #endregion Serialization } diff --git a/src/System.Management.Automation/engine/hostifaces/LocalPipeline.cs b/src/System.Management.Automation/engine/hostifaces/LocalPipeline.cs index 93cb65f3f07..a9a3a66b859 100644 --- a/src/System.Management.Automation/engine/hostifaces/LocalPipeline.cs +++ b/src/System.Management.Automation/engine/hostifaces/LocalPipeline.cs @@ -1067,9 +1067,9 @@ internal override void SetHistoryString(string historyString) /// ExecutionContext, if it available in TLS /// Null, if ExecutionContext is not available in TLS /// </returns> - internal static System.Management.Automation.ExecutionContext GetExecutionContextFromTLS() + internal static ExecutionContext GetExecutionContextFromTLS() { - System.Management.Automation.Runspaces.Runspace runspace = Runspace.DefaultRunspace; + Runspace runspace = Runspace.DefaultRunspace; if (runspace == null) { return null; diff --git a/src/System.Management.Automation/engine/hostifaces/MshHostRawUserInterface.cs b/src/System.Management.Automation/engine/hostifaces/MshHostRawUserInterface.cs index 201caf3c827..64b200b7d61 100644 --- a/src/System.Management.Automation/engine/hostifaces/MshHostRawUserInterface.cs +++ b/src/System.Management.Automation/engine/hostifaces/MshHostRawUserInterface.cs @@ -62,7 +62,7 @@ public int Y } /// <summary> - /// Overrides <see cref="System.Object.ToString"/> + /// Overrides <see cref="object.ToString"/> /// </summary> /// <returns> /// "a,b" where a and b are the values of the X and Y properties. @@ -75,7 +75,7 @@ public override } /// <summary> - /// Overrides <see cref="System.Object.Equals(object)"/> + /// Overrides <see cref="object.Equals(object)"/> /// </summary> /// <param name="obj"> /// object to be compared for equality. @@ -99,7 +99,7 @@ public override } /// <summary> - /// Overrides <see cref="System.Object.GetHashCode"/> + /// Overrides <see cref="object.GetHashCode"/> /// </summary> /// <returns> /// Hash code for this instance. @@ -248,7 +248,7 @@ public int Height } /// <summary> - /// Overloads <see cref="System.Object.ToString"/> + /// Overloads <see cref="object.ToString"/> /// </summary> /// <returns> /// "a,b" where a and b are the values of the Width and Height properties. @@ -261,7 +261,7 @@ public override } /// <summary> - /// Overrides <see cref="System.Object.Equals(object)"/> + /// Overrides <see cref="object.Equals(object)"/> /// </summary> /// <param name="obj"> /// object to be compared for equality. @@ -285,7 +285,7 @@ public override } /// <summary> - /// Overrides <see cref="System.Object.GetHashCode"/> + /// Overrides <see cref="object.GetHashCode"/> /// </summary> /// <returns> /// Hash code for this instance. @@ -557,7 +557,7 @@ bool keyDown } /// <summary> - /// Overloads <see cref="System.Object.ToString"/> + /// Overloads <see cref="object.ToString"/> /// </summary> /// <returns> /// "a,b,c,d" where a, b, c, and d are the values of the VirtualKeyCode, Character, ControlKeyState, and KeyDown properties. @@ -569,7 +569,7 @@ public override return string.Create(CultureInfo.InvariantCulture, $"{VirtualKeyCode},{Character},{ControlKeyState},{KeyDown}"); } /// <summary> - /// Overrides <see cref="System.Object.Equals(object)"/> + /// Overrides <see cref="object.Equals(object)"/> /// </summary> /// <param name="obj"> /// object to be compared for equality. @@ -593,7 +593,7 @@ public override } /// <summary> - /// Overrides <see cref="System.Object.GetHashCode"/> + /// Overrides <see cref="object.GetHashCode"/> /// </summary> /// <returns> /// Hash code for this instance. @@ -787,7 +787,7 @@ public int Bottom } /// <summary> - /// Overloads <see cref="System.Object.ToString"/> + /// Overloads <see cref="object.ToString"/> /// </summary> /// <returns> /// "a,b ; c,d" where a, b, c, and d are values of the Left, Top, Right, and Bottom properties. @@ -800,7 +800,7 @@ public override } /// <summary> - /// Overrides <see cref="System.Object.Equals(object)"/> + /// Overrides <see cref="object.Equals(object)"/> /// </summary> /// <param name="obj"> /// object to be compared for equality. @@ -824,7 +824,7 @@ public override } /// <summary> - /// Overrides <see cref="System.Object.GetHashCode"/> + /// Overrides <see cref="object.GetHashCode"/> /// </summary> /// <returns> /// Hash code for this instance. @@ -1015,7 +1015,7 @@ public BufferCellType BufferCellType } /// <summary> - /// Overloads <see cref="System.Object.ToString"/> + /// Overloads <see cref="object.ToString"/> /// </summary> /// <returns> /// "'a' b c d" where a, b, c, and d are the values of the Character, ForegroundColor, BackgroundColor, and Type properties. @@ -1028,7 +1028,7 @@ public override } /// <summary> - /// Overrides <see cref="System.Object.Equals(object)"/> + /// Overrides <see cref="object.Equals(object)"/> /// </summary> /// <param name="obj"> /// object to be compared for equality. @@ -1052,7 +1052,7 @@ public override } /// <summary> - /// Overrides <see cref="System.Object.GetHashCode"/> + /// Overrides <see cref="object.GetHashCode"/> /// <!-- consider (ForegroundColor XOR BackgroundColor) the high-order part of a 32-bit int, /// and Character the lower order half. Then use the int32.GetHashCode.--> /// </summary> diff --git a/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs b/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs index 29fc5fa1f7f..5f51ba15751 100644 --- a/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs +++ b/src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs @@ -1156,6 +1156,11 @@ internal static string GetTranscriptPath(string baseDirectory, bool includeDate) } } + if (string.IsNullOrEmpty(baseDirectory)) + { + return string.Empty; + } + if (includeDate) { baseDirectory = Path.Combine(baseDirectory, DateTime.Now.ToString("yyyyMMdd", CultureInfo.InvariantCulture)); diff --git a/src/System.Management.Automation/engine/hostifaces/PSDataCollection.cs b/src/System.Management.Automation/engine/hostifaces/PSDataCollection.cs index eb1a796d0e7..a0945e21df1 100644 --- a/src/System.Management.Automation/engine/hostifaces/PSDataCollection.cs +++ b/src/System.Management.Automation/engine/hostifaces/PSDataCollection.cs @@ -335,7 +335,7 @@ protected PSDataCollection(SerializationInfo info, StreamingContext context) throw PSTraceSource.NewArgumentNullException(nameof(info)); } - if (!(info.GetValue("Data", typeof(IList<T>)) is IList<T> listToUse)) + if (info.GetValue("Data", typeof(IList<T>)) is not IList<T> listToUse) { throw PSTraceSource.NewArgumentNullException(nameof(info)); } diff --git a/src/System.Management.Automation/engine/hostifaces/PSTask.cs b/src/System.Management.Automation/engine/hostifaces/PSTask.cs index c56225f3bb7..56bdabbff14 100644 --- a/src/System.Management.Automation/engine/hostifaces/PSTask.cs +++ b/src/System.Management.Automation/engine/hostifaces/PSTask.cs @@ -952,7 +952,7 @@ private Runspace GetRunspace(int taskId) iss.LanguageMode = PSLanguageMode.FullLanguage; break; } - + runspace = RunspaceFactory.CreateRunspace(iss); runspace.Name = runspaceName; _activeRunspaces.TryAdd(runspace.Id, runspace); diff --git a/src/System.Management.Automation/engine/hostifaces/Pipeline.cs b/src/System.Management.Automation/engine/hostifaces/Pipeline.cs index 66ebf7d0287..f7bca01fe7b 100644 --- a/src/System.Management.Automation/engine/hostifaces/Pipeline.cs +++ b/src/System.Management.Automation/engine/hostifaces/Pipeline.cs @@ -85,7 +85,7 @@ internal InvalidPipelineStateException(string message, PipelineState currentStat /// The <see cref="StreamingContext"/> that contains contextual information /// about the source or destination. /// </param> - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] private InvalidPipelineStateException(SerializationInfo info, StreamingContext context) { throw new NotSupportedException(); diff --git a/src/System.Management.Automation/engine/hostifaces/PowerShell.cs b/src/System.Management.Automation/engine/hostifaces/PowerShell.cs index 7d29ffa5223..3af978ea165 100644 --- a/src/System.Management.Automation/engine/hostifaces/PowerShell.cs +++ b/src/System.Management.Automation/engine/hostifaces/PowerShell.cs @@ -96,7 +96,7 @@ internal InvalidPowerShellStateException(PSInvocationState currentState) /// The <see cref="StreamingContext"/> that contains contextual information /// about the source or destination. /// </param> - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected InvalidPowerShellStateException(SerializationInfo info, StreamingContext context) { @@ -1369,7 +1369,7 @@ public PowerShell AddParameters(IDictionary parameters) foreach (DictionaryEntry entry in parameters) { - if (!(entry.Key is string parameterName)) + if (entry.Key is not string parameterName) { throw PSTraceSource.NewArgumentException(nameof(parameters), PowerShellStrings.KeyMustBeString); } @@ -3348,7 +3348,7 @@ public Task<PSDataCollection<PSObject>> InvokeAsync<TInput, TOutput>(PSDataColle /// </exception> private IAsyncResult BeginBatchInvoke<TInput, TOutput>(PSDataCollection<TInput> input, PSDataCollection<TOutput> output, PSInvocationSettings settings, AsyncCallback callback, object state) { - if (!((object)output is PSDataCollection<PSObject> asyncOutput)) + if ((object)output is not PSDataCollection<PSObject> asyncOutput) { throw PSTraceSource.NewInvalidOperationException(); } @@ -3736,7 +3736,7 @@ public PSDataCollection<PSObject> EndInvoke(IAsyncResult asyncResult) /// </exception> /// <remarks> /// When used with <see cref="PowerShell.Invoke()"/>, that call will return a partial result. - /// When used with <see cref="PowerShell.InvokeAsync"/>, that call will throw a <see cref="System.Management.Automation.PipelineStoppedException"/>. + /// When used with <see cref="PowerShell.InvokeAsync"/>, that call will throw a <see cref="System.Management.Automation.PipelineStoppedException"/>. /// </remarks> public void Stop() { @@ -3796,7 +3796,7 @@ public IAsyncResult BeginStop(AsyncCallback callback, object state) /// </exception> /// <remarks> /// When used with <see cref="PowerShell.Invoke()"/>, that call will return a partial result. - /// When used with <see cref="PowerShell.InvokeAsync"/>, that call will throw a <see cref="System.Management.Automation.PipelineStoppedException"/>. + /// When used with <see cref="PowerShell.InvokeAsync"/>, that call will throw a <see cref="System.Management.Automation.PipelineStoppedException"/>. /// </remarks> public void EndStop(IAsyncResult asyncResult) { @@ -3850,7 +3850,7 @@ public void EndStop(IAsyncResult asyncResult) /// </exception> /// <remarks> /// When used with <see cref="PowerShell.Invoke()"/>, that call will return a partial result. - /// When used with <see cref="PowerShell.InvokeAsync"/>, that call will throw a <see cref="System.Management.Automation.PipelineStoppedException"/>. + /// When used with <see cref="PowerShell.InvokeAsync"/>, that call will throw a <see cref="System.Management.Automation.PipelineStoppedException"/>. /// </remarks> public Task StopAsync(AsyncCallback callback, object state) => Task.Factory.FromAsync(BeginStop(callback, state), _endStopMethod); @@ -3880,15 +3880,50 @@ private void PipelineStateChanged(object source, PipelineStateEventArgs stateEve #region IDisposable Overrides /// <summary> - /// Dispose all managed resources. This will suppress finalizer on the object from getting called by - /// calling System.GC.SuppressFinalize(this). + /// Release all resources. /// </summary> public void Dispose() { - Dispose(true); - // To prevent derived types with finalizers from having to re-implement System.IDisposable to call it, - // unsealed types without finalizers should still call SuppressFinalize. - System.GC.SuppressFinalize(this); + lock (_syncObject) + { + // if already disposed return + if (_isDisposed) + { + return; + } + } + + // Stop the currently running command outside of the lock + if (InvocationStateInfo.State == PSInvocationState.Running || + InvocationStateInfo.State == PSInvocationState.Stopping) + { + Stop(); + } + + lock (_syncObject) + { + _isDisposed = true; + } + + if (OutputBuffer != null && OutputBufferOwner) + { + OutputBuffer.Dispose(); + } + + if (_errorBuffer != null && ErrorBufferOwner) + { + _errorBuffer.Dispose(); + } + + if (IsRunspaceOwner) + { + _runspace.Dispose(); + } + + RemotePowerShell?.Dispose(); + + _invokeAsyncResult = null; + _stopAsyncResult = null; } #endregion @@ -4121,59 +4156,6 @@ private void AssertNotDisposed() } } - /// <summary> - /// Release all the resources. - /// </summary> - /// <param name="disposing"> - /// if true, release all the managed objects. - /// </param> - private void Dispose(bool disposing) - { - if (disposing) - { - lock (_syncObject) - { - // if already disposed return - if (_isDisposed) - { - return; - } - } - - // Stop the currently running command outside of the lock - if (InvocationStateInfo.State == PSInvocationState.Running || - InvocationStateInfo.State == PSInvocationState.Stopping) - { - Stop(); - } - - lock (_syncObject) - { - _isDisposed = true; - } - - if (OutputBuffer != null && OutputBufferOwner) - { - OutputBuffer.Dispose(); - } - - if (_errorBuffer != null && ErrorBufferOwner) - { - _errorBuffer.Dispose(); - } - - if (IsRunspaceOwner) - { - _runspace.Dispose(); - } - - RemotePowerShell?.Dispose(); - - _invokeAsyncResult = null; - _stopAsyncResult = null; - } - } - /// <summary> /// Clear the internal elements. /// </summary> diff --git a/src/System.Management.Automation/engine/hostifaces/PowerShellProcessInstance.cs b/src/System.Management.Automation/engine/hostifaces/PowerShellProcessInstance.cs index 5ab95041877..f86d2c00a54 100644 --- a/src/System.Management.Automation/engine/hostifaces/PowerShellProcessInstance.cs +++ b/src/System.Management.Automation/engine/hostifaces/PowerShellProcessInstance.cs @@ -179,15 +179,9 @@ public bool HasExited #region Dispose /// <summary> - /// Implementing the <see cref="IDisposable"/> interface. + /// Release all resources. /// </summary> public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private void Dispose(bool disposing) { if (_isDisposed) { @@ -203,23 +197,20 @@ private void Dispose(bool disposing) _isDisposed = true; } - - if (disposing) + + try + { + if (Process != null && !Process.HasExited) + Process.Kill(); + } + catch (InvalidOperationException) + { + } + catch (Win32Exception) + { + } + catch (NotSupportedException) { - try - { - if (Process != null && !Process.HasExited) - Process.Kill(); - } - catch (InvalidOperationException) - { - } - catch (Win32Exception) - { - } - catch (NotSupportedException) - { - } } } diff --git a/src/System.Management.Automation/engine/hostifaces/RunspaceInvoke.cs b/src/System.Management.Automation/engine/hostifaces/RunspaceInvoke.cs index 5fa8370bcea..dcce30264ec 100644 --- a/src/System.Management.Automation/engine/hostifaces/RunspaceInvoke.cs +++ b/src/System.Management.Automation/engine/hostifaces/RunspaceInvoke.cs @@ -161,4 +161,3 @@ protected virtual void Dispose(bool disposing) #endregion IDisposable Members } } - diff --git a/src/System.Management.Automation/engine/hostifaces/RunspacePool.cs b/src/System.Management.Automation/engine/hostifaces/RunspacePool.cs index 5b03ecffa58..e7cfb888a88 100644 --- a/src/System.Management.Automation/engine/hostifaces/RunspacePool.cs +++ b/src/System.Management.Automation/engine/hostifaces/RunspacePool.cs @@ -90,7 +90,7 @@ RunspacePoolState expectedState /// The <see cref="StreamingContext"/> that contains /// contextual information about the source or destination. /// </param> - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected InvalidRunspacePoolStateException(SerializationInfo info, StreamingContext context) { @@ -1199,7 +1199,7 @@ public void EndClose(IAsyncResult asyncResult) } /// <summary> - /// Dispose the current runspacepool. + /// Release all resources. /// </summary> public void Dispose() { diff --git a/src/System.Management.Automation/engine/hostifaces/RunspacePoolInternal.cs b/src/System.Management.Automation/engine/hostifaces/RunspacePoolInternal.cs index 6df0aa74695..1b20fc4c85f 100644 --- a/src/System.Management.Automation/engine/hostifaces/RunspacePoolInternal.cs +++ b/src/System.Management.Automation/engine/hostifaces/RunspacePoolInternal.cs @@ -812,7 +812,7 @@ public void ReleaseRunspace(Runspace runspace) EnqueueCheckAndStartRequestServicingThread(null, false); } } - + /// <summary> /// Release all resources. /// </summary> diff --git a/src/System.Management.Automation/engine/interpreter/InterpretedFrame.cs b/src/System.Management.Automation/engine/interpreter/InterpretedFrame.cs index 4b084ba72a8..977fee85803 100644 --- a/src/System.Management.Automation/engine/interpreter/InterpretedFrame.cs +++ b/src/System.Management.Automation/engine/interpreter/InterpretedFrame.cs @@ -27,23 +27,19 @@ namespace System.Management.Automation.Interpreter { internal sealed class InterpretedFrame { - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] public static readonly ThreadLocal<InterpretedFrame> CurrentFrame = new ThreadLocal<InterpretedFrame>(); internal readonly Interpreter Interpreter; internal InterpretedFrame _parent; - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2105:ArrayFieldsShouldNotBeReadOnly")] private readonly int[] _continuations; private int _continuationIndex; private int _pendingContinuation; private object _pendingValue; - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2105:ArrayFieldsShouldNotBeReadOnly")] public readonly object[] Data; - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2105:ArrayFieldsShouldNotBeReadOnly")] public readonly StrongBox<object>[] Closure; public int StackIndex; diff --git a/src/System.Management.Automation/engine/interpreter/LightCompiler.cs b/src/System.Management.Automation/engine/interpreter/LightCompiler.cs index a006b7e0f4f..c117fbff33a 100644 --- a/src/System.Management.Automation/engine/interpreter/LightCompiler.cs +++ b/src/System.Management.Automation/engine/interpreter/LightCompiler.cs @@ -1298,7 +1298,7 @@ private bool TryPushLabelBlock(Expression node) private void DefineBlockLabels(Expression node) { - if (!(node is BlockExpression block)) + if (node is not BlockExpression block) { return; } diff --git a/src/System.Management.Automation/engine/lang/parserutils.cs b/src/System.Management.Automation/engine/lang/parserutils.cs index eb438a8a057..184c82c6815 100644 --- a/src/System.Management.Automation/engine/lang/parserutils.cs +++ b/src/System.Management.Automation/engine/lang/parserutils.cs @@ -1466,7 +1466,7 @@ internal static string GetTypeFullName(object obj) return string.Empty; } - if (!(obj is PSObject mshObj)) + if (obj is not PSObject mshObj) { return obj.GetType().FullName; } @@ -1570,7 +1570,7 @@ internal static object CallMethod( // not really a method call. if (valueToSet != AutomationNull.Value) { - if (!(targetMethod is PSParameterizedProperty propertyToSet)) + if (targetMethod is not PSParameterizedProperty propertyToSet) { throw InterpreterError.NewInterpreterException(methodName, typeof(RuntimeException), errorPosition, "ParameterizedPropertyAssignmentFailed", ParserStrings.ParameterizedPropertyAssignmentFailed, GetTypeFullName(target), methodName); diff --git a/src/System.Management.Automation/engine/lang/scriptblock.cs b/src/System.Management.Automation/engine/lang/scriptblock.cs index 7bbd33c9cfd..06fa66d0f7c 100644 --- a/src/System.Management.Automation/engine/lang/scriptblock.cs +++ b/src/System.Management.Automation/engine/lang/scriptblock.cs @@ -104,7 +104,7 @@ internal static ScriptBlock Create(ExecutionContext context, string script) /// </summary> /// <param name="script">The string to compile.</param> public static ScriptBlock Create(string script) => Create( - parser: new Language.Parser(), + parser: new Parser(), fileName: null, fileContents: script); diff --git a/src/System.Management.Automation/engine/parser/SafeValues.cs b/src/System.Management.Automation/engine/parser/SafeValues.cs index 05c87daab5b..38181168a66 100644 --- a/src/System.Management.Automation/engine/parser/SafeValues.cs +++ b/src/System.Management.Automation/engine/parser/SafeValues.cs @@ -47,11 +47,15 @@ public static bool IsAstSafe(Ast ast, GetSafeValueVisitor.SafeValueContext safeV internal IsSafeValueVisitor(GetSafeValueVisitor.SafeValueContext safeValueContext) { _safeValueContext = safeValueContext; + + bool skipSizeCheck = safeValueContext is GetSafeValueVisitor.SafeValueContext.SkipHashtableSizeCheck; + _maxVisitCount = skipSizeCheck ? uint.MaxValue : 5000; + _maxHashtableKeyCount = skipSizeCheck ? int.MaxValue : 500; } internal bool IsAstSafe(Ast ast) { - if ((bool)ast.Accept(this) && _visitCount < MaxVisitCount) + if ((bool)ast.Accept(this) && _visitCount < _maxVisitCount) { return true; } @@ -65,8 +69,8 @@ internal bool IsAstSafe(Ast ast) // This is a check of the number of visits private uint _visitCount = 0; - private const uint MaxVisitCount = 5000; - private const int MaxHashtableKeyCount = 500; + private readonly uint _maxVisitCount; + private readonly int _maxHashtableKeyCount; // Used to determine if we are being called within a GetPowerShell() context, // which does some additional security verification outside of the scope of @@ -330,7 +334,7 @@ public object VisitArrayLiteral(ArrayLiteralAst arrayLiteralAst) public object VisitHashtable(HashtableAst hashtableAst) { - if (hashtableAst.KeyValuePairs.Count > MaxHashtableKeyCount) + if (hashtableAst.KeyValuePairs.Count > _maxHashtableKeyCount) { return false; } @@ -373,7 +377,7 @@ public static object GetSafeValue(Ast ast, ExecutionContext context, SafeValueCo { t_context = context; - if (safeValueContext == SafeValueContext.SkipHashtableSizeCheck || IsSafeValueVisitor.IsAstSafe(ast, safeValueContext)) + if (IsSafeValueVisitor.IsAstSafe(ast, safeValueContext)) { return ast.Accept(new GetSafeValueVisitor()); } diff --git a/src/System.Management.Automation/engine/parser/TypeResolver.cs b/src/System.Management.Automation/engine/parser/TypeResolver.cs index 72258a4f459..73d44e94fbe 100644 --- a/src/System.Management.Automation/engine/parser/TypeResolver.cs +++ b/src/System.Management.Automation/engine/parser/TypeResolver.cs @@ -745,7 +745,7 @@ internal static class CoreTypes { typeof(Guid), new[] { "guid" } }, { typeof(Hashtable), new[] { "hashtable" } }, { typeof(int), new[] { "int", "int32" } }, - { typeof(Int16), new[] { "short", "int16" } }, + { typeof(short), new[] { "short", "int16" } }, { typeof(long), new[] { "long", "int64" } }, { typeof(CimInstance), new[] { "ciminstance" } }, { typeof(CimClass), new[] { "cimclass" } }, @@ -778,9 +778,9 @@ internal static class CoreTypes { typeof(BigInteger), new[] { "bigint" } }, { typeof(SecureString), new[] { "securestring" } }, { typeof(TimeSpan), new[] { "timespan" } }, - { typeof(UInt16), new[] { "ushort", "uint16" } }, - { typeof(UInt32), new[] { "uint", "uint32" } }, - { typeof(UInt64), new[] { "ulong", "uint64" } }, + { typeof(ushort), new[] { "ushort", "uint16" } }, + { typeof(uint), new[] { "uint", "uint32" } }, + { typeof(ulong), new[] { "ulong", "uint64" } }, { typeof(Uri), new[] { "uri" } }, { typeof(ValidateCountAttribute), new[] { "ValidateCount" } }, { typeof(ValidateDriveAttribute), new[] { "ValidateDrive" } }, diff --git a/src/System.Management.Automation/engine/parser/ast.cs b/src/System.Management.Automation/engine/parser/ast.cs index 0325cf94aeb..ba5c48a2752 100644 --- a/src/System.Management.Automation/engine/parser/ast.cs +++ b/src/System.Management.Automation/engine/parser/ast.cs @@ -7939,7 +7939,7 @@ public override Ast Copy() /// <summary> /// The static type produced after the cast is normally the type named by <see cref="Type"/>, but in some cases - /// it may not be, in which, <see cref="Object"/> is assumed. + /// it may not be, in which, <see cref="object"/> is assumed. /// </summary> public override Type StaticType { diff --git a/src/System.Management.Automation/engine/regex.cs b/src/System.Management.Automation/engine/regex.cs index cead035f057..6e0ef9741d6 100644 --- a/src/System.Management.Automation/engine/regex.cs +++ b/src/System.Management.Automation/engine/regex.cs @@ -73,18 +73,6 @@ public sealed class WildcardPattern // Default is WildcardOptions.None. internal WildcardOptions Options { get; } - /// <summary> - /// Wildcard pattern converted to regex pattern. - /// </summary> - internal string PatternConvertedToRegex - { - get - { - var patternRegex = WildcardPatternToRegexParser.Parse(this); - return patternRegex.ToString(); - } - } - /// <summary> /// Initializes and instance of the WildcardPattern class /// for the specified wildcard pattern. @@ -205,6 +193,35 @@ public bool IsMatch(string input) return input != null && _isMatch(input); } + /// <summary> + /// Converts the wildcard pattern to its regular expression equivalent. + /// </summary> + /// <returns> + /// A <see cref="Regex"/> object that represents the regular expression equivalent of the wildcard pattern. + /// The regex is configured with options matching the wildcard pattern's options. + /// </returns> + /// <remarks> + /// This method converts a wildcard pattern to a regular expression. + /// The conversion follows these rules: + /// <list type="bullet"> + /// <item><description>* (asterisk) converts to .* (matches any string)</description></item> + /// <item><description>? (question mark) converts to . (matches any single character)</description></item> + /// <item><description>[abc] (bracket expression) converts to [abc] (matches any character in the set)</description></item> + /// <item><description>Literal characters are escaped as needed for regex</description></item> + /// </list> + /// </remarks> + /// <example> + /// <code> + /// var pattern = new WildcardPattern("*.txt"); + /// Regex regex = pattern.ToRegex(); + /// // regex.ToString() returns: "\.txt$" + /// </code> + /// </example> + public Regex ToRegex() + { + return WildcardPatternToRegexParser.Parse(this); + } + /// <summary> /// Escape special chars, except for those specified in <paramref name="charsNotToEscape"/>, in a string by replacing them with their escape codes. /// </summary> @@ -335,7 +352,7 @@ internal static bool ContainsRangeWildcard(string pattern) foundStart = true; continue; } - + if (foundStart && pattern[index] is ']') { result = true; @@ -524,7 +541,7 @@ public WildcardPatternException(string message, /// </summary> /// <param name="info">Serialization information.</param> /// <param name="context">Streaming context.</param> - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected WildcardPatternException(SerializationInfo info, StreamingContext context) { diff --git a/src/System.Management.Automation/engine/remoting/client/ClientMethodExecutor.cs b/src/System.Management.Automation/engine/remoting/client/ClientMethodExecutor.cs index 73c432158f3..9d52bcb4dc9 100644 --- a/src/System.Management.Automation/engine/remoting/client/ClientMethodExecutor.cs +++ b/src/System.Management.Automation/engine/remoting/client/ClientMethodExecutor.cs @@ -133,7 +133,7 @@ internal static void Dispatch( /// </summary> private static bool IsRunspacePushed(PSHost host) { - if (!(host is IHostSupportsInteractiveSession host2)) + if (host is not IHostSupportsInteractiveSession host2) { return false; } diff --git a/src/System.Management.Automation/engine/remoting/client/Job.cs b/src/System.Management.Automation/engine/remoting/client/Job.cs index b3a0135afc7..adf47d04cab 100644 --- a/src/System.Management.Automation/engine/remoting/client/Job.cs +++ b/src/System.Management.Automation/engine/remoting/client/Job.cs @@ -752,10 +752,10 @@ private void WriteError(Cmdlet cmdlet, ErrorRecord errorRecord) private static Exception GetExceptionFromErrorRecord(ErrorRecord errorRecord) { - if (!(errorRecord.Exception is RuntimeException runtimeException)) + if (errorRecord.Exception is not RuntimeException runtimeException) return null; - if (!(runtimeException is RemoteException remoteException)) + if (runtimeException is not RemoteException remoteException) return null; PSPropertyInfo wasThrownFromThrow = @@ -1911,7 +1911,7 @@ internal List<Job> GetJobsForComputer(string computerName) foreach (Job j in ChildJobs) { - if (!(j is PSRemotingChildJob child)) continue; + if (j is not PSRemotingChildJob child) continue; if (string.Equals(child.Runspace.ConnectionInfo.ComputerName, computerName, StringComparison.OrdinalIgnoreCase)) { @@ -1934,7 +1934,7 @@ internal List<Job> GetJobsForRunspace(PSSession runspace) foreach (Job j in ChildJobs) { - if (!(j is PSRemotingChildJob child)) continue; + if (j is not PSRemotingChildJob child) continue; if (child.Runspace.InstanceId.Equals(runspace.InstanceId)) { returnJobList.Add(child); @@ -1957,7 +1957,7 @@ internal List<Job> GetJobsForOperation(IThrottleOperation operation) foreach (Job j in ChildJobs) { - if (!(j is PSRemotingChildJob child)) continue; + if (j is not PSRemotingChildJob child) continue; if (child.Helper.Equals(helper)) { returnJobList.Add(child); diff --git a/src/System.Management.Automation/engine/remoting/client/JobSourceAdapter.cs b/src/System.Management.Automation/engine/remoting/client/JobSourceAdapter.cs index 9f6297fbfab..85cd6c7ba1f 100644 --- a/src/System.Management.Automation/engine/remoting/client/JobSourceAdapter.cs +++ b/src/System.Management.Automation/engine/remoting/client/JobSourceAdapter.cs @@ -413,7 +413,7 @@ public void StoreJobIdForReuse(Job2 job, bool recurse) duplicateDetector.Add(job.InstanceId, job.InstanceId); foreach (Job child in job.ChildJobs) { - if (!(child is Job2 childJob)) continue; + if (child is not Job2 childJob) continue; StoreJobIdForReuseHelper(duplicateDetector, childJob, true); } } @@ -429,7 +429,7 @@ private void StoreJobIdForReuseHelper(Hashtable duplicateDetector, Job2 job, boo if (!recurse || job.ChildJobs == null) return; foreach (Job child in job.ChildJobs) { - if (!(child is Job2 childJob)) continue; + if (child is not Job2 childJob) continue; StoreJobIdForReuseHelper(duplicateDetector, childJob, recurse); } } diff --git a/src/System.Management.Automation/engine/remoting/client/RemoteRunspacePoolInternal.cs b/src/System.Management.Automation/engine/remoting/client/RemoteRunspacePoolInternal.cs index c414b5095e6..983180bd5ca 100644 --- a/src/System.Management.Automation/engine/remoting/client/RemoteRunspacePoolInternal.cs +++ b/src/System.Management.Automation/engine/remoting/client/RemoteRunspacePoolInternal.cs @@ -1237,7 +1237,7 @@ public override RunspacePoolCapability GetCapabilities() internal static RunspacePool[] GetRemoteRunspacePools(RunspaceConnectionInfo connectionInfo, PSHost host, TypeTable typeTable) { - if (!(connectionInfo is WSManConnectionInfo wsmanConnectionInfoParam)) + if (connectionInfo is not WSManConnectionInfo wsmanConnectionInfoParam) { // Disconnect-Connect currently only supported by WSMan. throw new NotSupportedException(); diff --git a/src/System.Management.Automation/engine/remoting/client/RemotingProtocol2.cs b/src/System.Management.Automation/engine/remoting/client/RemotingProtocol2.cs index 4c5aec933b4..441a3f62fed 100644 --- a/src/System.Management.Automation/engine/remoting/client/RemotingProtocol2.cs +++ b/src/System.Management.Automation/engine/remoting/client/RemotingProtocol2.cs @@ -1551,7 +1551,7 @@ private void HandleInputDataReady(object sender, EventArgs e) /// <param name="inputstream"></param> private void WriteInput(ObjectStreamBase inputstream) { - Collection<object> inputObjects = inputstream.ObjectReader.NonBlockingRead(Int32.MaxValue); + Collection<object> inputObjects = inputstream.ObjectReader.NonBlockingRead(int.MaxValue); foreach (object inputObject in inputObjects) { @@ -1562,7 +1562,7 @@ private void WriteInput(ObjectStreamBase inputstream) if (!inputstream.IsOpen) { // Write any data written after the NonBlockingRead call above. - inputObjects = inputstream.ObjectReader.NonBlockingRead(Int32.MaxValue); + inputObjects = inputstream.ObjectReader.NonBlockingRead(int.MaxValue); foreach (object inputObject in inputObjects) { diff --git a/src/System.Management.Automation/engine/remoting/client/remoterunspaceinfo.cs b/src/System.Management.Automation/engine/remoting/client/remoterunspaceinfo.cs index 55c2b23db75..c1b34691104 100644 --- a/src/System.Management.Automation/engine/remoting/client/remoterunspaceinfo.cs +++ b/src/System.Management.Automation/engine/remoting/client/remoterunspaceinfo.cs @@ -374,7 +374,7 @@ public static PSSession Create( string transportName, PSCmdlet psCmdlet) { - if (!(runspace is RemoteRunspace remoteRunspace)) + if (runspace is not RemoteRunspace remoteRunspace) { throw new PSArgumentException(RemotingErrorIdStrings.InvalidPSSessionArgument); } diff --git a/src/System.Management.Automation/engine/remoting/commands/CustomShellCommands.cs b/src/System.Management.Automation/engine/remoting/commands/CustomShellCommands.cs index e600661eab9..1d88210e7b6 100644 --- a/src/System.Management.Automation/engine/remoting/commands/CustomShellCommands.cs +++ b/src/System.Management.Automation/engine/remoting/commands/CustomShellCommands.cs @@ -1753,7 +1753,7 @@ internal static string CreateConditionalACEFromConfig( } StringBuilder conditionalACE = new StringBuilder(); - if (!(configTable[ConfigFileConstants.RequiredGroups] is Hashtable requiredGroupsHash)) + if (configTable[ConfigFileConstants.RequiredGroups] is not Hashtable requiredGroupsHash) { throw new PSInvalidOperationException(RemotingErrorIdStrings.RequiredGroupsNotHashTable); } diff --git a/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs b/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs index 2145a1dbe2a..e340a1acbdb 100644 --- a/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/InvokeCommandCommand.cs @@ -226,7 +226,7 @@ public override PSCredential Credential [Parameter(ParameterSetName = InvokeCommandCommand.ComputerNameParameterSet)] [Parameter(ParameterSetName = InvokeCommandCommand.FilePathComputerNameParameterSet)] [Parameter(ParameterSetName = InvokeCommandCommand.SSHHostParameterSet)] - [ValidateRange((int)1, (int)UInt16.MaxValue)] + [ValidateRange((int)1, (int)ushort.MaxValue)] public override int Port { get @@ -1050,7 +1050,7 @@ protected override void BeginProcessing() // create collection of input writers here foreach (IThrottleOperation operation in Operations) { - if (!(operation is ExecutionCmdletHelperRunspace ecHelper)) + if (operation is not ExecutionCmdletHelperRunspace ecHelper) { // either all the operations will be of type ExecutionCmdletHelperRunspace // or not...there is no mix. diff --git a/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs b/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs index 8a5df652470..ee06dc22130 100644 --- a/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs +++ b/src/System.Management.Automation/engine/remoting/commands/PSRemotingCmdlet.cs @@ -606,7 +606,7 @@ public virtual PSCredential Credential /// </remarks> [Parameter(ParameterSetName = PSRemotingBaseCmdlet.ComputerNameParameterSet)] [Parameter(ParameterSetName = PSRemotingBaseCmdlet.SSHHostParameterSet)] - [ValidateRange((int)1, (int)UInt16.MaxValue)] + [ValidateRange((int)1, (int)ushort.MaxValue)] public virtual int Port { get; set; } /// <summary> @@ -4149,7 +4149,7 @@ internal static string ExtractMessage( try { // WinRM returns both signed and unsigned 32 bit string values. Convert to signed 32 bit integer. - Int64 eCode = Convert.ToInt64(errorCodeString, System.Globalization.NumberFormatInfo.InvariantInfo); + long eCode = Convert.ToInt64(errorCodeString, System.Globalization.NumberFormatInfo.InvariantInfo); unchecked { errorCode = (int)eCode; diff --git a/src/System.Management.Automation/engine/remoting/commands/ReceivePSSession.cs b/src/System.Management.Automation/engine/remoting/commands/ReceivePSSession.cs index b9cf33e4b1c..67477e5b36f 100644 --- a/src/System.Management.Automation/engine/remoting/commands/ReceivePSSession.cs +++ b/src/System.Management.Automation/engine/remoting/commands/ReceivePSSession.cs @@ -1166,7 +1166,7 @@ private static PSSession ConnectSession(PSSession session, out Exception ex) /// <returns>PSSession disconnected runspace object.</returns> private PSSession TryGetSessionFromServer(PSSession session) { - if (!(session.Runspace is RemoteRunspace remoteRunspace)) + if (session.Runspace is not RemoteRunspace remoteRunspace) { return null; } diff --git a/src/System.Management.Automation/engine/remoting/commands/StartJob.cs b/src/System.Management.Automation/engine/remoting/commands/StartJob.cs index 05611d8c0ae..9913ec64ced 100644 --- a/src/System.Management.Automation/engine/remoting/commands/StartJob.cs +++ b/src/System.Management.Automation/engine/remoting/commands/StartJob.cs @@ -277,7 +277,7 @@ public override string ConfigurationName /// <summary> /// Overriding to suppress this parameter. /// </summary> - public override Int32 ThrottleLimit + public override int ThrottleLimit { get { diff --git a/src/System.Management.Automation/engine/remoting/commands/WaitJob.cs b/src/System.Management.Automation/engine/remoting/commands/WaitJob.cs index 3bbc4463fe8..6964a1154b5 100644 --- a/src/System.Management.Automation/engine/remoting/commands/WaitJob.cs +++ b/src/System.Management.Automation/engine/remoting/commands/WaitJob.cs @@ -46,7 +46,7 @@ public class WaitJobCommand : JobCmdletBase, IDisposable /// </summary> [Parameter] [Alias("TimeoutSec")] - [ValidateRange(-1, Int32.MaxValue)] + [ValidateRange(-1, int.MaxValue)] public int Timeout { get diff --git a/src/System.Management.Automation/engine/remoting/commands/getrunspacecommand.cs b/src/System.Management.Automation/engine/remoting/commands/getrunspacecommand.cs index fb6bf348aba..22af9adf9ff 100644 --- a/src/System.Management.Automation/engine/remoting/commands/getrunspacecommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/getrunspacecommand.cs @@ -283,7 +283,7 @@ public string CertificateThumbprint /// </remarks> [Parameter(ParameterSetName = GetPSSessionCommand.ComputerNameParameterSet)] [Parameter(ParameterSetName = GetPSSessionCommand.ComputerInstanceIdParameterSet)] - [ValidateRange((int)1, (int)UInt16.MaxValue)] + [ValidateRange((int)1, (int)ushort.MaxValue)] public int Port { get; set; } /// <summary> diff --git a/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs b/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs index 8986def5a6d..60ca0c35e50 100644 --- a/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/newrunspacecommand.cs @@ -267,7 +267,16 @@ protected override void ProcessRecord() case NewPSSessionCommand.UseWindowsPowerShellParameterSet: { - remoteRunspaces = CreateRunspacesForUseWindowsPowerShellParameterSet(); + if (UseWindowsPowerShell) + { + remoteRunspaces = CreateRunspacesForUseWindowsPowerShellParameterSet(); + } + else + { + // When -UseWindowsPowerShell:$false is explicitly specified, + // fall back to the default ComputerName parameter set behavior + goto case NewPSSessionCommand.ComputerNameParameterSet; + } } break; diff --git a/src/System.Management.Automation/engine/remoting/common/PSETWTracer.cs b/src/System.Management.Automation/engine/remoting/common/PSETWTracer.cs index 2fd2dc0a913..989ad33e987 100644 --- a/src/System.Management.Automation/engine/remoting/common/PSETWTracer.cs +++ b/src/System.Management.Automation/engine/remoting/common/PSETWTracer.cs @@ -166,6 +166,9 @@ internal enum PSEventId : int ExperimentalFeature_InvalidName = 0x3001, ExperimentalFeature_ReadConfig_Error = 0x3002, + // Windows Diagnostics And Usage Data Settings + Telemetry_Setting_Error = 0x3011, + // Scheduled Jobs ScheduledJob_Start = 0xD001, ScheduledJob_Complete = 0xD002, @@ -240,6 +243,7 @@ internal enum PSTask : int ProviderStop = 0x69, ExecutePipeline = 0x6A, ExperimentalFeature = 0x6B, + Telemetry = 0x6C, ScheduledJob = 0x6E, NamedPipe = 0x6F, ISEOperation = 0x78, diff --git a/src/System.Management.Automation/engine/remoting/common/RemoteSessionHyperVSocket.cs b/src/System.Management.Automation/engine/remoting/common/RemoteSessionHyperVSocket.cs index 94ce5af0208..8d53adf0ca8 100644 --- a/src/System.Management.Automation/engine/remoting/common/RemoteSessionHyperVSocket.cs +++ b/src/System.Management.Automation/engine/remoting/common/RemoteSessionHyperVSocket.cs @@ -7,8 +7,10 @@ using System.Net.Sockets; using System.Text; using System.Threading; +using System.Buffers; using Dbg = System.Diagnostics.Debug; +using SMA = System.Management.Automation; namespace System.Management.Automation.Remoting { @@ -140,6 +142,10 @@ internal sealed class RemoteSessionHyperVSocketServer : IDisposable private readonly object _syncObject; private readonly PowerShellTraceSource _tracer = PowerShellTraceSourceFactory.GetTraceSource(); + // This is to prevent persistent replay attacks. + // it is not meant to ensure all replay attacks are impossible. + private const int MAX_TOKEN_LIFE_MINUTES = 10; + #endregion #region Properties @@ -175,64 +181,74 @@ internal sealed class RemoteSessionHyperVSocketServer : IDisposable public RemoteSessionHyperVSocketServer(bool LoopbackMode) { - // TODO: uncomment below code when .NET supports Hyper-V socket duplication - /* - NamedPipeClientStream clientPipeStream; - byte[] buffer = new byte[1000]; - int bytesRead; - */ _syncObject = new object(); Exception ex = null; try { - // TODO: uncomment below code when .NET supports Hyper-V socket duplication - /* - if (!LoopbackMode) - { - // - // Create named pipe client. - // - using (clientPipeStream = new NamedPipeClientStream(".", - "PS_VMSession", - PipeDirection.InOut, - PipeOptions.None, - TokenImpersonationLevel.None)) - { - // - // Connect to named pipe server. - // - clientPipeStream.Connect(10*1000); - - // - // Read LPWSAPROTOCOL_INFO. - // - bytesRead = clientPipeStream.Read(buffer, 0, 1000); - } - } + Guid serviceId = new Guid("a5201c21-2770-4c11-a68e-f182edb29220"); // HV_GUID_VM_SESSION_SERVICE_ID_2 + Guid loopbackId = new Guid("e0e16197-dd56-4a10-9195-5ee7a155a838"); // HV_GUID_LOOPBACK + Guid parentId = new Guid("a42e7cda-d03f-480c-9cc2-a4de20abb878"); // HV_GUID_PARENT + Guid vmId = LoopbackMode ? loopbackId : parentId; + HyperVSocketEndPoint endpoint = new HyperVSocketEndPoint(HyperVSocketEndPoint.AF_HYPERV, vmId, serviceId); + + Socket listenSocket = new Socket(endpoint.AddressFamily, SocketType.Stream, (System.Net.Sockets.ProtocolType)1); + listenSocket.Bind(endpoint); + + listenSocket.Listen(1); + HyperVSocket = listenSocket.Accept(); + + Stream = new NetworkStream(HyperVSocket, true); + + // Create reader/writer streams. + TextReader = new StreamReader(Stream); + TextWriter = new StreamWriter(Stream); + TextWriter.AutoFlush = true; // - // Create duplicate socket. + // listenSocket is not closed when it goes out of scope here. Sometimes it is + // closed later in this thread, while other times it is not closed at all. This will + // cause problem when we set up a second PowerShell Direct session. Let's + // explicitly close listenSocket here for safe. // - byte[] protocolInfo = new byte[bytesRead]; - Array.Copy(buffer, protocolInfo, bytesRead); + if (listenSocket != null) + { + try { listenSocket.Dispose(); } + catch (ObjectDisposedException) { } + } + } + catch (Exception e) + { + ex = e; + } - SocketInformation sockInfo = new SocketInformation(); - sockInfo.ProtocolInformation = protocolInfo; - sockInfo.Options = SocketInformationOptions.Connected; + if (ex != null) + { + Dbg.Fail("Unexpected error in RemoteSessionHyperVSocketServer."); - socket = new Socket(sockInfo); - if (socket == null) - { - Dbg.Assert(false, "Unexpected error in RemoteSessionHyperVSocketServer."); + // Unexpected error. + string errorMessage = !string.IsNullOrEmpty(ex.Message) ? ex.Message : string.Empty; + _tracer.WriteMessage("RemoteSessionHyperVSocketServer", "RemoteSessionHyperVSocketServer", Guid.Empty, + "Unexpected error in constructor: {0}", errorMessage); - tracer.WriteMessage("RemoteSessionHyperVSocketServer", "RemoteSessionHyperVSocketServer", Guid.Empty, - "Unexpected error in constructor: {0}", "socket duplication failure"); - } - */ + throw new PSInvalidOperationException( + PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.RemoteSessionHyperVSocketServerConstructorFailure), + ex, + nameof(PSRemotingErrorId.RemoteSessionHyperVSocketServerConstructorFailure), + ErrorCategory.InvalidOperation, + null); + } + } + + public RemoteSessionHyperVSocketServer(bool LoopbackMode, string token, DateTimeOffset tokenCreationTime) + { + _syncObject = new object(); + + Exception ex = null; - // TODO: remove below 6 lines of code when .NET supports Hyper-V socket duplication + try + { Guid serviceId = new Guid("a5201c21-2770-4c11-a68e-f182edb29220"); // HV_GUID_VM_SESSION_SERVICE_ID_2 HyperVSocketEndPoint endpoint = new HyperVSocketEndPoint(HyperVSocketEndPoint.AF_HYPERV, Guid.Empty, serviceId); @@ -242,6 +258,8 @@ public RemoteSessionHyperVSocketServer(bool LoopbackMode) listenSocket.Listen(1); HyperVSocket = listenSocket.Accept(); + ValidateToken(HyperVSocket, token, tokenCreationTime, MAX_TOKEN_LIFE_MINUTES * 60); + Stream = new NetworkStream(HyperVSocket, true); // Create reader/writer streams. @@ -257,8 +275,13 @@ public RemoteSessionHyperVSocketServer(bool LoopbackMode) // if (listenSocket != null) { - try { listenSocket.Dispose(); } - catch (ObjectDisposedException) { } + try + { + listenSocket.Dispose(); + } + catch (ObjectDisposedException) + { + } } } catch (Exception e) @@ -272,8 +295,12 @@ public RemoteSessionHyperVSocketServer(bool LoopbackMode) // Unexpected error. string errorMessage = !string.IsNullOrEmpty(ex.Message) ? ex.Message : string.Empty; - _tracer.WriteMessage("RemoteSessionHyperVSocketServer", "RemoteSessionHyperVSocketServer", Guid.Empty, - "Unexpected error in constructor: {0}", errorMessage); + _tracer.WriteMessage( + "RemoteSessionHyperVSocketServer", + "RemoteSessionHyperVSocketServer", + Guid.Empty, + "Unexpected error in constructor: {0}", + errorMessage); throw new PSInvalidOperationException( PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.RemoteSessionHyperVSocketServerConstructorFailure), @@ -283,7 +310,6 @@ public RemoteSessionHyperVSocketServer(bool LoopbackMode) null); } } - #endregion #region IDisposable @@ -333,6 +359,107 @@ public void Dispose() } #endregion + + /// <summary> + /// Validates the token received from the client over the HyperVSocket. + /// Throws PSDirectException if the token is invalid or not received in time. + /// </summary> + /// <param name="socket">The connected HyperVSocket.</param> + /// <param name="token">The expected token string.</param> + /// <param name="tokenCreationTime">The creation time of the token.</param> + /// <param name="maxTokenLifeSeconds">The maximum lifetime of the token in seconds.</param> + internal static void ValidateToken(Socket socket, string token, DateTimeOffset tokenCreationTime, int maxTokenLifeSeconds) + { + TimeSpan timeout = TimeSpan.FromSeconds(maxTokenLifeSeconds); + DateTimeOffset timeoutExpiry = tokenCreationTime.Add(timeout); + DateTimeOffset now = DateTimeOffset.UtcNow; + + // Calculate remaining time and create cancellation token + TimeSpan remainingTime = timeoutExpiry - now; + + // Check if the token has already expired + if (remainingTime <= TimeSpan.Zero) + { + throw new PSDirectException( + PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.InvalidCredential, "Token has expired")); + } + + // Create a cancellation token that will be cancelled when the timeout expires + using var cancellationTokenSource = new CancellationTokenSource(remainingTime); + CancellationToken cancellationToken = cancellationTokenSource.Token; + + // Set socket timeout for receive operations to prevent indefinite blocking + int timeoutMs = (int)remainingTime.TotalMilliseconds; + socket.ReceiveTimeout = timeoutMs; + socket.SendTimeout = timeoutMs; + + // Check for cancellation before starting validation + cancellationToken.ThrowIfCancellationRequested(); + + // We should move to this pattern and + // in the tests I found I needed to get a bigger buffer than the token length + // and test length of the received data similar to this pattern. + string responseString = RemoteSessionHyperVSocketClient.ReceiveResponse(socket, RemoteSessionHyperVSocketClient.VERSION_REQUEST.Length + 4); + if (string.IsNullOrEmpty(responseString) || responseString.Length != RemoteSessionHyperVSocketClient.VERSION_REQUEST.Length) + { + socket.Send("FAIL"u8); + throw new PSDirectException( + PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.HyperVInvalidResponse, "Client", "Version Request: " + responseString)); + } + + cancellationToken.ThrowIfCancellationRequested(); + + socket.Send(Encoding.UTF8.GetBytes(RemoteSessionHyperVSocketClient.CLIENT_VERSION)); + responseString = RemoteSessionHyperVSocketClient.ReceiveResponse(socket, RemoteSessionHyperVSocketClient.CLIENT_VERSION.Length + 4); + + // In the future we may need to handle different versions, differently. + // For now, we are just checking that we exchanged versions correctly. + if (string.IsNullOrEmpty(responseString) || !responseString.StartsWith(RemoteSessionHyperVSocketClient.VERSION_PREFIX, StringComparison.Ordinal)) + { + socket.Send("FAIL"u8); + throw new PSDirectException( + PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.HyperVInvalidResponse, "Client", "Version Response: " + responseString)); + } + + cancellationToken.ThrowIfCancellationRequested(); + + socket.Send("PASS"u8); + + // The client should send the token in the format TOKEN <token> + // the token should be up to 256 bits, which is less than 50 characters. + // I'll double that to 100 characters to be safe, plus the "TOKEN " prefix. + // So we expect a response of length 6 + 100 = 106 characters. + responseString = RemoteSessionHyperVSocketClient.ReceiveResponse(socket, 110); + + // Final check if we got the token before the timeout + cancellationToken.ThrowIfCancellationRequested(); + + ReadOnlySpan<byte> responseBytes = Encoding.UTF8.GetBytes(responseString); + string responseToken = RemoteSessionHyperVSocketClient.ExtractToken(responseBytes); + + if (responseToken == null) + { + socket.Send("FAIL"u8); + // If the response is not in the expected format, we throw an exception. + // This is a failure to authenticate the client. + // don't send this response for risk of information disclosure. + throw new PSDirectException( + PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.HyperVInvalidResponse, "Client", "Token Response")); + } + + if (!string.Equals(responseToken, token, StringComparison.Ordinal)) + { + socket.Send("FAIL"u8); + throw new PSDirectException( + PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.InvalidCredential)); + } + + // Acknowledge the token is valid with "PASS". + socket.Send("PASS"u8); + + socket.ReceiveTimeout = 0; // Disable the timeout after successful validation + socket.SendTimeout = 0; + } } internal sealed class RemoteSessionHyperVSocketClient : IDisposable @@ -340,7 +467,15 @@ internal sealed class RemoteSessionHyperVSocketClient : IDisposable #region Members private readonly object _syncObject; - private readonly PowerShellTraceSource _tracer = PowerShellTraceSourceFactory.GetTraceSource(); + + #region tracer + /// <summary> + /// An instance of the PSTraceSource class used for trace output. + /// </summary> + [SMA.TraceSource("RemoteSessionHyperVSocketClient", "Class that has PowerShell Direct Client implementation")] + private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("RemoteSessionHyperVSocketClient", "Class that has PowerShell Direct Client implementation"); + + #endregion tracer private static readonly ManualResetEvent s_connectDone = new ManualResetEvent(false); @@ -354,6 +489,14 @@ internal sealed class RemoteSessionHyperVSocketClient : IDisposable #endregion + #region version constants + + internal const string VERSION_REQUEST = "VERSION"; + internal const string CLIENT_VERSION = "VERSION_2"; + internal const string VERSION_PREFIX = "VERSION_"; + + #endregion + #region Properties /// <summary> @@ -364,7 +507,7 @@ internal sealed class RemoteSessionHyperVSocketClient : IDisposable /// <summary> /// Returns the Hyper-V socket object. /// </summary> - public Socket HyperVSocket { get; } + public Socket HyperVSocket { get; private set; } /// <summary> /// Returns the network stream object. @@ -381,6 +524,37 @@ internal sealed class RemoteSessionHyperVSocketClient : IDisposable /// </summary> public StreamWriter TextWriter { get; private set; } + /// <summary> + /// True if the client is a Hyper-V container. + /// </summary> + public bool IsContainer { get; } + + /// <summary> + /// True if the client is using backwards compatible mode. + /// This is used to determine if the client should use + /// the backwards compatible or not. + /// In modern mode, the vmicvmsession service will + /// hand off the socket to the PowerShell process + /// inside the VM automatically. + /// In backwards compatible mode, the vmicvmsession + /// service create a new socket to the PowerShell process + /// inside the VM. + /// </summary> + public bool UseBackwardsCompatibleMode { get; private set; } + + /// <summary> + /// The authentication token used for the session. + /// This token is provided by the broker and provided to the server to authenticate the server session. + /// This protocol uses two connections: + /// 1. The first is to the broker or vmicvmsession service to exchange credentials and configuration. + /// The broker will respond with an authentication token. The broker also launches a PowerShell + /// server process with the authentication token. + /// 2. The second is to the server process, that was launched by the broker, + /// inside the VM, which uses the authentication token to verify that the client is the same client + /// that connected to the broker. + /// </summary> + public string AuthenticationToken { get; private set; } + /// <summary> /// Returns true if object is currently disposed. /// </summary> @@ -393,7 +567,9 @@ internal sealed class RemoteSessionHyperVSocketClient : IDisposable internal RemoteSessionHyperVSocketClient( Guid vmId, bool isFirstConnection, - bool isContainer = false) + bool useBackwardsCompatibleMode = false, + bool isContainer = false, + string authenticationToken = null) { Guid serviceId; @@ -412,28 +588,16 @@ internal RemoteSessionHyperVSocketClient( EndPoint = new HyperVSocketEndPoint(HyperVSocketEndPoint.AF_HYPERV, vmId, serviceId); - HyperVSocket = new Socket(EndPoint.AddressFamily, SocketType.Stream, (System.Net.Sockets.ProtocolType)1); + IsContainer = isContainer; - // - // We need to call SetSocketOption() in order to set up Hyper-V socket connection between container host and Hyper-V container. - // Here is the scenario: the Hyper-V container is inside a utility vm, which is inside the container host - // - if (isContainer) - { - var value = new byte[sizeof(uint)]; - value[0] = 1; + UseBackwardsCompatibleMode = useBackwardsCompatibleMode; - try - { - HyperVSocket.SetSocketOption((System.Net.Sockets.SocketOptionLevel)HV_PROTOCOL_RAW, - (System.Net.Sockets.SocketOptionName)HVSOCKET_CONTAINER_PASSTHRU, - (byte[])value); - } - catch - { - throw new PSDirectException( - PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.RemoteSessionHyperVSocketClientConstructorSetSocketOptionFailure)); - } + if (!isFirstConnection && !useBackwardsCompatibleMode && !string.IsNullOrEmpty(authenticationToken)) + { + // If this is not the first connection and we are using backwards compatible mode, + // we should not set the authentication token here. + // The authentication token will be set during the Connect method. + AuthenticationToken = authenticationToken; } } @@ -489,6 +653,81 @@ public void Dispose() #region Public Methods + private void ShutdownSocket() + { + if (HyperVSocket != null) + { + // Ensure the socket is disposed properly. + try + { + s_tracer.WriteLine("ShutdownSocket: Disposing of the HyperVSocket."); + HyperVSocket.Dispose(); + } + catch (Exception ex) + { + s_tracer.WriteLine("ShutdownSocket: Exception while disposing the socket: {0}", ex.Message); + } + } + + // Dispose of the existing stream if it exists. + if (Stream != null) + { + try + { + Stream.Dispose(); + } + catch (Exception ex) + { + s_tracer.WriteLine("ShutdownSocket: Exception while disposing the stream: {0}", ex.Message); + } + } + } + + /// <summary> + /// Recreates the HyperVSocket and connects it to the endpoint, updating the Stream if successful. + /// </summary> + private bool ConnectSocket() + { + HyperVSocket = new Socket(EndPoint.AddressFamily, SocketType.Stream, (System.Net.Sockets.ProtocolType)1); + + // + // We need to call SetSocketOption() in order to set up Hyper-V socket connection between container host and Hyper-V container. + // Here is the scenario: the Hyper-V container is inside a utility vm, which is inside the container host + // + if (IsContainer) + { + var value = new byte[sizeof(uint)]; + value[0] = 1; + + try + { + HyperVSocket.SetSocketOption( + (System.Net.Sockets.SocketOptionLevel)HV_PROTOCOL_RAW, + (System.Net.Sockets.SocketOptionName)HVSOCKET_CONTAINER_PASSTHRU, + value); + } + catch + { + throw new PSDirectException( + PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.RemoteSessionHyperVSocketClientConstructorSetSocketOptionFailure)); + } + } + + s_tracer.WriteLine("Connect: Client connecting, to {0}; isContainer: {1}.", EndPoint.ServiceId.ToString(), IsContainer); + HyperVSocket.Connect(EndPoint); + + // Check if the socket is connected. + // If it is connected, create a NetworkStream. + if (HyperVSocket.Connected) + { + s_tracer.WriteLine("Connect: Client connected, to {0}; isContainer: {1}.", EndPoint.ServiceId.ToString(), IsContainer); + Stream = new NetworkStream(HyperVSocket, true); + return true; + } + + return false; + } + /// <summary> /// Connect to Hyper-V socket server. This is a blocking call until a /// connection occurs or the timeout time has elapsed. @@ -516,100 +755,51 @@ public bool Connect( } } - HyperVSocket.Connect(EndPoint); - - if (HyperVSocket.Connected) + if (ConnectSocket()) { - _tracer.WriteMessage("RemoteSessionHyperVSocketClient", "Connect", Guid.Empty, - "Client connected."); - - Stream = new NetworkStream(HyperVSocket, true); - if (isFirstConnection) { - if (string.IsNullOrEmpty(networkCredential.Domain)) + var exchangeResult = ExchangeCredentialsAndConfiguration(networkCredential, configurationName, HyperVSocket, this.UseBackwardsCompatibleMode); + if (!exchangeResult.success) { - networkCredential.Domain = "localhost"; - } + // We will not block here for a container because a container does not have a broker. + if (IsRequirePsDirectAuthenticationEnabled(@"SOFTWARE\\Microsoft\\PowerShell", Microsoft.Win32.RegistryHive.LocalMachine)) + { + s_tracer.WriteLine("ExchangeCredentialsAndConfiguration: RequirePsDirectAuthentication is enabled, requiring latest transport version."); + throw new PSDirectException( + PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.HyperVNegotiationFailed)); + } - bool emptyPassword = string.IsNullOrEmpty(networkCredential.Password); - bool emptyConfiguration = string.IsNullOrEmpty(configurationName); - - byte[] domain = Encoding.Unicode.GetBytes(networkCredential.Domain); - byte[] userName = Encoding.Unicode.GetBytes(networkCredential.UserName); - byte[] password = Encoding.Unicode.GetBytes(networkCredential.Password); - byte[] response = new byte[4]; // either "PASS" or "FAIL" - string responseString; - - // - // Send credential to VM so that PowerShell process inside VM can be - // created under the correct security context. - // - HyperVSocket.Send(domain); - HyperVSocket.Receive(response); - - HyperVSocket.Send(userName); - HyperVSocket.Receive(response); - - // - // We cannot simply send password because if it is empty, - // the vmicvmsession service in VM will block in recv method. - // - if (emptyPassword) - { - HyperVSocket.Send("EMPTYPW"u8); - HyperVSocket.Receive(response); - responseString = Encoding.ASCII.GetString(response); - } - else - { - HyperVSocket.Send("NONEMPTYPW"u8); - HyperVSocket.Receive(response); + this.UseBackwardsCompatibleMode = true; + s_tracer.WriteLine("ExchangeCredentialsAndConfiguration: Using backwards compatible mode."); - HyperVSocket.Send(password); - HyperVSocket.Receive(response); - responseString = Encoding.ASCII.GetString(response); + // If the first connection fails in modern mode, fall back to backwards compatible mode. + ShutdownSocket(); // will terminate the broker + ConnectSocket(); // restart the broker + exchangeResult = ExchangeCredentialsAndConfiguration(networkCredential, configurationName, HyperVSocket, this.UseBackwardsCompatibleMode); + if (!exchangeResult.success) + { + s_tracer.WriteLine("ExchangeCredentialsAndConfiguration: Failed to exchange credentials and configuration in backwards compatible mode."); + throw new PSDirectException( + PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.HyperVInvalidResponse, "Broker", "Credential")); + } } - - // - // There are 3 cases for the responseString received above. - // - "FAIL": credential is invalid - // - "PASS": credential is valid, but PowerShell Direct in VM does not support configuration (Server 2016 TP4 and before) - // - "CONF": credential is valid, and PowerShell Direct in VM supports configuration (Server 2016 TP5 and later) - // - - // - // Credential is invalid. - // - if (string.Equals(responseString, "FAIL", StringComparison.Ordinal)) + else { - HyperVSocket.Send(response); - - throw new PSDirectException( - PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.InvalidCredential)); + this.AuthenticationToken = exchangeResult.authenticationToken; } + } - // - // If PowerShell Direct in VM supports configuration, send configuration name. - // - if (string.Equals(responseString, "CONF", StringComparison.Ordinal)) + if (!isFirstConnection) + { + if (!this.UseBackwardsCompatibleMode) { - if (emptyConfiguration) - { - HyperVSocket.Send("EMPTYCF"u8); - } - else - { - HyperVSocket.Send("NONEMPTYCF"u8); - HyperVSocket.Receive(response); - - byte[] configName = Encoding.Unicode.GetBytes(configurationName); - HyperVSocket.Send(configName); - } + s_tracer.WriteLine("Connect-Server: Performing transport version and token exchange for Hyper-V socket. isFirstConnection: {0}, UseBackwardsCompatibleMode: {1}", isFirstConnection, this.UseBackwardsCompatibleMode); + RemoteSessionHyperVSocketClient.PerformTransportVersionAndTokenExchange(HyperVSocket, this.AuthenticationToken); } else { - HyperVSocket.Send(response); + s_tracer.WriteLine("Connect-Server: Skipping transport version and token exchange for backwards compatible mode."); } } @@ -621,8 +811,7 @@ public bool Connect( } else { - _tracer.WriteMessage("RemoteSessionHyperVSocketClient", "Connect", Guid.Empty, - "Client unable to connect."); + s_tracer.WriteLine("Connect: Client unable to connect."); result = false; } @@ -630,12 +819,341 @@ public bool Connect( return result; } + /// <summary> + /// Performs the transport version and token exchange sequence for the Hyper-V socket connection. + /// Throws PSDirectException on failure. + /// </summary> + /// <param name="socket">The socket to use for communication.</param> + /// <param name="authenticationToken">The authentication token to send.</param> + public static void PerformTransportVersionAndTokenExchange(Socket socket, string authenticationToken) + { + if (string.IsNullOrEmpty(authenticationToken)) + { + s_tracer.WriteLine("PerformTransportVersionAndTokenExchange: Authentication token is null or empty. Aborting transport version and token exchange."); + throw new PSDirectException( + PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.InvalidCredential)); + } + + socket.Send(Encoding.UTF8.GetBytes(VERSION_REQUEST)); + string responseStr = ReceiveResponse(socket, 16); + + // Check if the response starts with the expected version prefix. + // We will rely on the broker to determine if the two can communicate. + // At least, for now. + if (!responseStr.StartsWith(VERSION_PREFIX, StringComparison.Ordinal)) + { + s_tracer.WriteLine("PerformTransportVersionAndTokenExchange: Server responded with an invalid response of {0}. Notifying the transport manager to downgrade if allowed.", responseStr); + throw new PSDirectException( + PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.HyperVInvalidResponse, "Server", "TransportVersion")); + } + + socket.Send(Encoding.UTF8.GetBytes(CLIENT_VERSION)); + string response = ReceiveResponse(socket, 4); // either "PASS" or "FAIL" + + if (!string.Equals(response, "PASS", StringComparison.Ordinal)) + { + s_tracer.WriteLine( + "PerformTransportVersionAndTokenExchange: Transport version negotiation with server failed. Response: {0}", response); + throw new PSDirectException( + PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.HyperVInvalidResponse, "Server", "TransportVersion")); + } + + byte[] tokenBytes = Encoding.UTF8.GetBytes("TOKEN " + authenticationToken); + socket.Send(tokenBytes); + + // This is the opportunity for the server to tell the client to go away. + string tokenResponse = ReceiveResponse(socket, 256); // either "PASS" or "FAIL", but get a little more buffer to allow for better error in the future + if (!string.Equals(tokenResponse, "PASS", StringComparison.Ordinal)) + { + s_tracer.WriteLine( + "PerformTransportVersionAndTokenExchange: Server Authentication Token exchange failed. Response: {0}", tokenResponse); + throw new PSDirectException( + PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.InvalidCredential)); + } + } + + /// <summary> + /// Checks if the registry key RequirePsDirectAuthentication is set to 1. + /// Returns true if fallback should be aborted. + /// Uses the 64-bit registry view on 64-bit systems to ensure consistent behavior regardless of process architecture. + /// On 32-bit systems, uses the default registry view since there is no WOW64 redirection. + /// </summary> + internal static bool IsRequirePsDirectAuthenticationEnabled(string keyPath, Microsoft.Win32.RegistryHive registryHive) + { + const string regValueName = "RequirePsDirectAuthentication"; + + try + { + Microsoft.Win32.RegistryView registryView = Environment.Is64BitOperatingSystem + ? Microsoft.Win32.RegistryView.Registry64 + : Microsoft.Win32.RegistryView.Default; + + using (Microsoft.Win32.RegistryKey baseKey = Microsoft.Win32.RegistryKey.OpenBaseKey( + registryHive, + registryView)) + { + using (Microsoft.Win32.RegistryKey key = baseKey.OpenSubKey(keyPath)) + { + if (key != null) + { + var value = key.GetValue(regValueName); + if (value is int intValue && intValue != 0) + { + return true; + } + } + + return false; + } + } + } + catch (Exception regEx) + { + s_tracer.WriteLine("IsRequirePsDirectAuthenticationEnabled: Exception while checking registry key: {0}", regEx.Message); + return false; // If we cannot read the registry, assume the feature is not enabled. + } + } + + /// <summary> + /// Handles credential and configuration exchange with the VM for the first connection. + /// </summary> + public static (bool success, string authenticationToken) ExchangeCredentialsAndConfiguration(NetworkCredential networkCredential, string configurationName, Socket HyperVSocket, bool useBackwardsCompatibleMode) + { + // Encoding for the Hyper-V socket communication + // To send the domain, username, password, and configuration name, use UTF-16 (Encoding.Unicode) + // All other sends use UTF-8 (Encoding.UTF8) + // Receiving uses ASCII encoding + // NOT CONFUSING AT ALL + + if (!useBackwardsCompatibleMode) + { + HyperVSocket.Send(Encoding.UTF8.GetBytes(VERSION_REQUEST)); + // vmicvmsession service in VM will respond with "VERSION_2" or newer + // Version 1 protocol will respond with "PASS" or "FAIL" + // Receive the response and check for VERSION_2 or newer + string responseStr = ReceiveResponse(HyperVSocket, 16); + if (!responseStr.StartsWith(VERSION_PREFIX, StringComparison.Ordinal)) + { + s_tracer.WriteLine("When asking for version the server responded with an invalid response of {0}.", responseStr); + s_tracer.WriteLine("Session is invalid, continuing session with a fake user to close the session with the broker for stability."); + // If not the new protocol, finish the conversation + // Send a fake user + // Use ? <> that are illegal in user names so no one can create the user + string probeUserName = "?<PSDirectVMLegacy>"; // must be less than or equal to 20 characters for Windows Server 2016 + s_tracer.WriteLine("probeUserName (static): length: {0}", probeUserName.Length); + SendUserData(probeUserName, HyperVSocket); + responseStr = ReceiveResponse(HyperVSocket, 4); // either "PASS" or "FAIL" + s_tracer.WriteLine("When sending user {0}.", responseStr); + + // Send that the password is empty + HyperVSocket.Send("EMPTYPW"u8); + responseStr = ReceiveResponse(HyperVSocket, 4); // either "CONF", "PASS" or "FAIL" + s_tracer.WriteLine("When sending EMPTYPW: {0}.", responseStr); // server responds with FAIL so we respond with FAIL and the conversation is done + HyperVSocket.Send("FAIL"u8); + + s_tracer.WriteLine("Notifying the transport manager to downgrade if allowed."); + // end new code + return (false, null); + } + + HyperVSocket.Send(Encoding.UTF8.GetBytes(CLIENT_VERSION)); + ReceiveResponse(HyperVSocket, 4); // either "PASS" or "FAIL" + } + + if (string.IsNullOrEmpty(networkCredential.Domain)) + { + networkCredential.Domain = "localhost"; + } + + System.Security.SecureString securePassword = networkCredential.SecurePassword; + int passwordLength = securePassword.Length; + bool emptyPassword = (passwordLength <= 0); + bool emptyConfiguration = string.IsNullOrEmpty(configurationName); + + string responseString; + + // Send credential to VM so that PowerShell process inside VM can be + // created under the correct security context. + SendUserData(networkCredential.Domain, HyperVSocket); + ReceiveResponse(HyperVSocket, 4); // only "PASS" is expected + + SendUserData(networkCredential.UserName, HyperVSocket); + ReceiveResponse(HyperVSocket, 4); // only "PASS" is expected + + // We cannot simply send password because if it is empty, + // the vmicvmsession service in VM will block in recv method. + if (emptyPassword) + { + HyperVSocket.Send("EMPTYPW"u8); + responseString = ReceiveResponse(HyperVSocket, 4); // either "CONF", "PASS" or "FAIL" (note, "PASS" is not used in VERSION_2 or newer mode) + } + else + { + HyperVSocket.Send("NONEMPTYPW"u8); + ReceiveResponse(HyperVSocket, 4); // only "PASS" is expected + + // Get the password bytes from the SecureString, send them, and then zero out the byte array. + byte[] passwordBytes = Microsoft.PowerShell.SecureStringHelper.GetData(securePassword); + try + { + HyperVSocket.Send(passwordBytes); + } + finally + { + // Zero out the byte array for security + Array.Clear(passwordBytes); + } + + responseString = ReceiveResponse(HyperVSocket, 4); // either "CONF", "PASS" or "FAIL" (note, "PASS" is not used in VERSION_2 or newer mode) + } + + // Check for invalid response from server + if (!string.Equals(responseString, "FAIL", StringComparison.Ordinal) && + !string.Equals(responseString, "PASS", StringComparison.Ordinal) && + !string.Equals(responseString, "CONF", StringComparison.Ordinal)) + { + s_tracer.WriteLine("ExchangeCredentialsAndConfiguration: Server responded with an invalid response of {0} for credentials.", responseString); + throw new PSDirectException( + PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.HyperVInvalidResponse, "Broker", "Credential")); + } + + // Credential is invalid. + if (string.Equals(responseString, "FAIL", StringComparison.Ordinal)) + { + HyperVSocket.Send("FAIL"u8); + // should we be doing this? Disabling the test for now + // HyperVSocket.Shutdown(SocketShutdown.Both); + s_tracer.WriteLine("ExchangeCredentialsAndConfiguration: Server responded with FAIL for credentials."); + throw new PSDirectException( + PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.InvalidCredential)); + } + + // If PowerShell Direct in VM supports configuration, send configuration name. + if (string.Equals(responseString, "CONF", StringComparison.Ordinal)) + { + if (emptyConfiguration) + { + HyperVSocket.Send("EMPTYCF"u8); + } + else + { + HyperVSocket.Send("NONEMPTYCF"u8); + ReceiveResponse(HyperVSocket, 4); // only "PASS" is expected + + SendUserData(configurationName, HyperVSocket); + } + } + else + { + HyperVSocket.Send("PASS"u8); + } + + if (!useBackwardsCompatibleMode) + { + // Receive the token from the server + // Getting 1024 bytes because it is well above the expected token size + // The expected size at the time of writing this would be about 50 based64 characters, + // plus the 6 characters for the "TOKEN " prefix. + // The 50 character size is designed to last 10 years of cryptographic changes. + // Since the broker completely controls the cryptographic portion here, + // allowing a significant larger size, allows the broker to make almost arbitrary changes, + // without breaking the client. + string token = ReceiveResponse(HyperVSocket, 1024); // either "PASS" or "FAIL" + + ReadOnlySpan<byte> tokenResponseBytes = Encoding.UTF8.GetBytes(token); + string extractedToken = ExtractToken(tokenResponseBytes); + + if (extractedToken == null) + { + s_tracer.WriteLine("ExchangeCredentialsAndConfiguration: Server did not respond with a valid token. Response: {0}", token); + throw new PSDirectException( + PSRemotingErrorInvariants.FormatResourceString(RemotingErrorIdStrings.HyperVInvalidResponse, "Broker", "Token " + token)); + } + + token = extractedToken; + + HyperVSocket.Send("PASS"u8); // acknowledge the token + return (true, token); + } + + return (true, null); + } + public void Close() { Stream.Dispose(); HyperVSocket.Dispose(); } + /// <summary> + /// Receives a response from the socket and decodes it. + /// </summary> + /// <param name="socket">The socket to receive from.</param> + /// <param name="bufferSize">The size of the buffer to use for receiving data.</param> + /// <returns>The decoded response string.</returns> + internal static string ReceiveResponse(Socket socket, int bufferSize) + { + System.Buffers.ArrayPool<byte> pool = System.Buffers.ArrayPool<byte>.Shared; + byte[] responseBuffer = pool.Rent(bufferSize); + int bytesReceived = 0; + try + { + bytesReceived = socket.Receive(responseBuffer); + if (bytesReceived == 0) + { + return null; + } + + string response = Encoding.ASCII.GetString(responseBuffer, 0, bytesReceived); + + // Handle null terminators and log if found + if (response.EndsWith('\0')) + { + int originalLength = response.Length; + response = response.TrimEnd('\0'); + // Cannot log actual response, because we don't know if it is sensitive + s_tracer.WriteLine( + "ReceiveResponse: Removed null terminator(s). Original length: {0}, New length: {1}", + originalLength, + response.Length); + } + + return response; + } + finally + { + pool.Return(responseBuffer); + } + } + + internal static string ExtractToken(ReadOnlySpan<byte> tokenResponse) + { + string token = Encoding.UTF8.GetString(tokenResponse); + + if (token == null || !token.StartsWith("TOKEN ", StringComparison.Ordinal)) + { + return null; // caller method will write trace (and determine when to expose token info as appropriate) + } + + token = token.Substring(6).Trim(); // remove "TOKEN " prefix + + if (token.Length == 0) + { + return null; + } + + return token; + } + + /// <summary> + /// Sends user data (domain, username, etc.) over the HyperVSocket using Unicode encoding. + /// </summary> + private static void SendUserData(string data, Socket socket) + { + // this encodes the data in UTF-16 (Unicode) + byte[] buffer = Encoding.Unicode.GetBytes(data); + socket.Send(buffer); + } #endregion } } diff --git a/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs b/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs index 4810d1b37ea..fc5226a007e 100644 --- a/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs +++ b/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs @@ -12,7 +12,6 @@ using System.Security.AccessControl; using System.Security.Principal; using System.Threading; -using System.Threading.Tasks; using Microsoft.Win32.SafeHandles; @@ -500,13 +499,14 @@ static RemoteSessionNamedPipeServer() { s_syncObject = new object(); - // All PowerShell instances will start with the named pipe - // and listener created and running. - IPCNamedPipeServerEnabled = true; - - CreateIPCNamedPipeServerSingleton(); + // Unless opt-out, all PowerShell instances will start with the named-pipe listener created and running. + IPCNamedPipeServerEnabled = !Utils.GetEnvironmentVariableAsBool(name: "POWERSHELL_DIAGNOSTICS_OPTOUT", defaultValue: false); - CreateProcessExitHandler(); + if (IPCNamedPipeServerEnabled) + { + CreateIPCNamedPipeServerSingleton(); + CreateProcessExitHandler(); + } } #endregion @@ -1302,7 +1302,6 @@ protected override NamedPipeClientStream DoConnect(int timeout) return new NamedPipeClientStream( PipeDirection.InOut, isAsync: true, - isConnected: true, pipeHandle); } catch (Exception) diff --git a/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs b/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs index b0f46e43830..475dc705674 100644 --- a/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs +++ b/src/System.Management.Automation/engine/remoting/common/RunspaceConnectionInfo.cs @@ -225,7 +225,7 @@ public int OpenTimeout // The timer constructor will throw an exception // for any value greater than Int32.MaxValue // hence this is the maximum possible limit - _openTimeout = Int32.MaxValue; + _openTimeout = int.MaxValue; } } } @@ -270,7 +270,7 @@ public int OpenTimeout /// The maximum allowed idle timeout duration (in ms) that can be set on a Runspace. This is a read-only property /// that is set once the Runspace is successfully created and opened. /// </summary> - public int MaxIdleTimeout { get; internal set; } = Int32.MaxValue; + public int MaxIdleTimeout { get; internal set; } = int.MaxValue; /// <summary> /// Populates session options from a PSSessionOption instance. @@ -1174,7 +1174,7 @@ private static string ResolveShellUri(string shell) internal static T ExtractPropertyAsWsManConnectionInfo<T>(RunspaceConnectionInfo rsCI, string property, T defaultValue) { - if (!(rsCI is WSManConnectionInfo wsCI)) + if (rsCI is not WSManConnectionInfo wsCI) { return defaultValue; } @@ -2211,11 +2211,11 @@ internal int StartSSHProcess( CommandTypes.Application, SearchResolutionOptions.None, CommandOrigin.Internal, - context) as ApplicationInfo; + context); - if (cmdInfo != null) + if (cmdInfo is ApplicationInfo appInfo) { - filePath = cmdInfo.Path; + filePath = appInfo.Path; } } else @@ -2255,7 +2255,7 @@ internal int StartSSHProcess( DiscoveryExceptions.CommandNotFoundException); } - // Create a local ssh process (client) that conects to a remote sshd process (server) using a 'powershell' subsystem. + // Create a local ssh process (client) that connects to a remote sshd process (server) using a 'powershell' subsystem. // // Local ssh invoked as: // windows: @@ -2272,13 +2272,14 @@ internal int StartSSHProcess( // linux|macos: // Subsystem powershell /usr/local/bin/pwsh -SSHServerMode -NoLogo -NoProfile - System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo(filePath); + // codeql[cs/microsoft/command-line-injection-shell-execution] - This is expected Poweshell behavior where user inputted paths are supported for the context of this method. The user assumes trust for the file path specified, so any file executed in the runspace would be in the user's local system/process or a system they have access to in which case restricted remoting security guidelines should be used. + ProcessStartInfo startInfo = new(filePath); // pass "-i identity_file" command line argument to ssh if KeyFilePath is set // if KeyFilePath is not set, then ssh will use IdentityFile / IdentityAgent from ssh_config if defined else none by default if (!string.IsNullOrEmpty(this.KeyFilePath)) { - if (!System.IO.File.Exists(this.KeyFilePath)) + if (!File.Exists(this.KeyFilePath)) { throw new FileNotFoundException( StringUtil.Format(RemotingErrorIdStrings.KeyFileNotFound, this.KeyFilePath)); @@ -2325,7 +2326,7 @@ internal int StartSSHProcess( // note that ssh expects IPv6 addresses to not be enclosed in square brackets so trim them if present startInfo.ArgumentList.Add(string.Create(CultureInfo.InvariantCulture, $@"-s {this.ComputerName.TrimStart('[').TrimEnd(']')} {this.Subsystem}")); - startInfo.WorkingDirectory = System.IO.Path.GetDirectoryName(filePath); + startInfo.WorkingDirectory = Path.GetDirectoryName(filePath); startInfo.CreateNoWindow = true; startInfo.UseShellExecute = false; @@ -2504,7 +2505,7 @@ private static string[] ParseArgv(ProcessStartInfo psi) var argvList = new List<string>(); argvList.Add(psi.FileName); - var argsToParse = String.Join(' ', psi.ArgumentList).Trim(); + var argsToParse = string.Join(' ', psi.ArgumentList).Trim(); var argsLength = argsToParse.Length; for (int i = 0; i < argsLength; ) { @@ -2579,7 +2580,7 @@ private static unsafe void AllocNullTerminatedArray(string[] arr, ref byte** arr // Allocate the unmanaged array to hold each string pointer. // It needs to have an extra element to null terminate the array. arrPtr = (byte**)Marshal.AllocHGlobal(sizeof(IntPtr) * arrLength); - System.Diagnostics.Debug.Assert(arrPtr != null, "Invalid array ptr"); + Debug.Assert(arrPtr != null, "Invalid array ptr"); // Zero the memory so that if any of the individual string allocations fails, // we can loop through the array to free any that succeeded. @@ -2596,7 +2597,7 @@ private static unsafe void AllocNullTerminatedArray(string[] arr, ref byte** arr byte[] byteArr = System.Text.Encoding.UTF8.GetBytes(arr[i]); arrPtr[i] = (byte*)Marshal.AllocHGlobal(byteArr.Length + 1); // +1 for null termination - System.Diagnostics.Debug.Assert(arrPtr[i] != null, "Invalid array ptr"); + Debug.Assert(arrPtr[i] != null, "Invalid array ptr"); Marshal.Copy(byteArr, 0, (IntPtr)arrPtr[i], byteArr.Length); // copy over the data from the managed byte array arrPtr[i][byteArr.Length] = (byte)'\0'; // null terminate @@ -2640,13 +2641,13 @@ internal static extern unsafe int ForkAndExecProcess( /// P-Invoking native APIs. /// </summary> private static int StartSSHProcessImpl( - System.Diagnostics.ProcessStartInfo startInfo, + ProcessStartInfo startInfo, out StreamWriter stdInWriterVar, out StreamReader stdOutReaderVar, out StreamReader stdErrReaderVar) { Exception ex = null; - System.Diagnostics.Process sshProcess = null; + Process sshProcess = null; // // These std pipe handles are bound to managed Reader/Writer objects and returned to the transport // manager object, which uses them for PSRP communication. The lifetime of these handles are then @@ -2667,7 +2668,7 @@ private static int StartSSHProcessImpl( catch (InvalidOperationException e) { ex = e; } catch (ArgumentException e) { ex = e; } catch (FileNotFoundException e) { ex = e; } - catch (System.ComponentModel.Win32Exception e) { ex = e; } + catch (Win32Exception e) { ex = e; } if ((ex != null) || (sshProcess == null) || @@ -2692,9 +2693,9 @@ private static int StartSSHProcessImpl( { if (stdInWriterVar != null) { stdInWriterVar.Dispose(); } else { stdInPipeServer.Dispose(); } - if (stdOutReaderVar != null) { stdInWriterVar.Dispose(); } else { stdOutPipeServer.Dispose(); } + if (stdOutReaderVar != null) { stdOutReaderVar.Dispose(); } else { stdOutPipeServer.Dispose(); } - if (stdErrReaderVar != null) { stdInWriterVar.Dispose(); } else { stdErrPipeServer.Dispose(); } + if (stdErrReaderVar != null) { stdErrReaderVar.Dispose(); } else { stdErrPipeServer.Dispose(); } throw; } @@ -2704,7 +2705,7 @@ private static int StartSSHProcessImpl( private static void KillSSHProcessImpl(int pid) { - using (var sshProcess = System.Diagnostics.Process.GetProcessById(pid)) + using (var sshProcess = Process.GetProcessById(pid)) { if ((sshProcess != null) && (sshProcess.Handle != IntPtr.Zero) && !sshProcess.HasExited) { @@ -2735,7 +2736,7 @@ private static Process CreateProcessWithRedirectedStd( SafeFileHandle stdInPipeClient = null; SafeFileHandle stdOutPipeClient = null; SafeFileHandle stdErrPipeClient = null; - string randomName = System.IO.Path.GetFileNameWithoutExtension(System.IO.Path.GetRandomFileName()); + string randomName = Path.GetFileNameWithoutExtension(Path.GetRandomFileName()); try { @@ -2828,16 +2829,14 @@ private static Process CreateProcessWithRedirectedStd( catch (Exception) { stdInPipeServer?.Dispose(); - stdInPipeClient?.Dispose(); stdOutPipeServer?.Dispose(); - stdOutPipeClient?.Dispose(); stdErrPipeServer?.Dispose(); - stdErrPipeClient?.Dispose(); throw; } finally { + lpStartupInfo.Dispose(); lpProcessInformation.Dispose(); } } diff --git a/src/System.Management.Automation/engine/remoting/common/WireDataFormat/EncodeAndDecode.cs b/src/System.Management.Automation/engine/remoting/common/WireDataFormat/EncodeAndDecode.cs index c7b06d4c74d..e26f3421cda 100644 --- a/src/System.Management.Automation/engine/remoting/common/WireDataFormat/EncodeAndDecode.cs +++ b/src/System.Management.Automation/engine/remoting/common/WireDataFormat/EncodeAndDecode.cs @@ -1869,7 +1869,7 @@ internal static object GetPowerShellOutput(object data) /// <returns>PSInvocationInfo.</returns> internal static PSInvocationStateInfo GetPowerShellStateInfo(object data) { - if (!(data is PSObject dataAsPSObject)) + if (data is not PSObject dataAsPSObject) { throw new PSRemotingDataStructureException( RemotingErrorIdStrings.DecodingErrorForPowerShellStateInfo); @@ -2137,7 +2137,7 @@ internal static RemoteStreamOptions GetRemoteStreamOptions(object data) /// <returns>RemoteSessionCapability object.</returns> internal static RemoteSessionCapability GetSessionCapability(object data) { - if (!(data is PSObject dataAsPSObject)) + if (data is not PSObject dataAsPSObject) { throw new PSRemotingDataStructureException( RemotingErrorIdStrings.CantCastRemotingDataToPSObject, data.GetType().FullName); diff --git a/src/System.Management.Automation/engine/remoting/common/fragmentor.cs b/src/System.Management.Automation/engine/remoting/common/fragmentor.cs index e0a82e46cf2..c451ba32f45 100644 --- a/src/System.Management.Automation/engine/remoting/common/fragmentor.cs +++ b/src/System.Management.Automation/engine/remoting/common/fragmentor.cs @@ -757,11 +757,11 @@ private void WriteCurrentFragmentAndReset() PSEtwLog.LogAnalyticVerbose( PSEventId.SentRemotingFragment, PSOpcode.Send, PSTask.None, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, - (Int64)(_currentFragment.ObjectId), - (Int64)(_currentFragment.FragmentId), + (long)(_currentFragment.ObjectId), + (long)(_currentFragment.FragmentId), _currentFragment.IsStartFragment ? 1 : 0, _currentFragment.IsEndFragment ? 1 : 0, - (UInt32)(_currentFragment.BlobLength), + (uint)(_currentFragment.BlobLength), new PSETWBinaryBlob(_currentFragment.Blob, 0, _currentFragment.BlobLength)); // finally write into memory stream diff --git a/src/System.Management.Automation/engine/remoting/fanin/BaseTransportManager.cs b/src/System.Management.Automation/engine/remoting/fanin/BaseTransportManager.cs index 0859a54965f..c3cf3b278aa 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/BaseTransportManager.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/BaseTransportManager.cs @@ -213,7 +213,7 @@ public abstract class BaseTransportManager : IDisposable // This value instructs the server to use whatever setting it has for idle timeout. internal const int UseServerDefaultIdleTimeout = -1; - internal const uint UseServerDefaultIdleTimeoutUInt = UInt32.MaxValue; + internal const uint UseServerDefaultIdleTimeoutUInt = uint.MaxValue; // Minimum allowed idle timeout time is 60 seconds. internal const int MinimumIdleTimeout = 60 * 1000; @@ -390,9 +390,9 @@ internal void OnDataAvailableCallback(RemoteDataObject<PSObject> remoteObject) PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, remoteObject.RunspacePoolId.ToString(), remoteObject.PowerShellId.ToString(), - (UInt32)(remoteObject.Destination), - (UInt32)(remoteObject.DataType), - (UInt32)(remoteObject.TargetInterface)); + (uint)(remoteObject.Destination), + (uint)(remoteObject.DataType), + (uint)(remoteObject.TargetInterface)); // This might throw exceptions which the caller handles. PowerShellGuidObserver.SafeInvoke(remoteObject.PowerShellId, EventArgs.Empty); @@ -1414,8 +1414,8 @@ private void OnDataAvailable(byte[] dataToSend, bool isEndFragment) _runspacePoolInstanceId.ToString(), _powerShellInstanceId.ToString(), dataToSend.Length.ToString(CultureInfo.InvariantCulture), - (UInt32)_dataType, - (UInt32)_targetInterface); + (uint)_dataType, + (uint)_targetInterface); SendDataToClient(dataToSend, isEndFragment && _shouldFlushData, _reportAsPending, isEndFragment); } diff --git a/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs b/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs index 94ffe3a68e6..07e17e8b63a 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs @@ -2836,7 +2836,7 @@ internal static Hashtable[] TryGetHashtableArray(object hashObj) for (int i = 0; i < hashArray.Length; i++) { - if (!(objArray[i] is Hashtable hash)) + if (objArray[i] is not Hashtable hash) { return null; } diff --git a/src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs b/src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs index 96a8b833885..47ff6270dba 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/OutOfProcTransportManager.cs @@ -1014,7 +1014,7 @@ internal void OnCloseTimeOutTimerElapsed(object source) } #endregion - + #region Protected Methods /// <summary> @@ -1544,8 +1544,9 @@ internal VMHyperVSocketClientSessionTransportManager( /// </summary> public override void CreateAsync() { - _client = new RemoteSessionHyperVSocketClient(_vmGuid, true); - if (!_client.Connect(_networkCredential, _configurationName, true)) + // isFirstConnection: true - specifies to use VM_SESSION_SERVICE_ID socket. + _client = new RemoteSessionHyperVSocketClient(_vmGuid, useBackwardsCompatibleMode: false, isFirstConnection: true); + if (!_client.Connect(_networkCredential, _configurationName, isFirstConnection: true)) { _client.Dispose(); throw new PSInvalidOperationException( @@ -1555,11 +1556,14 @@ public override void CreateAsync() ErrorCategory.InvalidOperation, null); } + bool useBackwardsCompatibleMode = _client.UseBackwardsCompatibleMode; + string token = _client.AuthenticationToken; - // TODO: remove below 3 lines when Hyper-V socket duplication is supported in .NET framework. _client.Dispose(); - _client = new RemoteSessionHyperVSocketClient(_vmGuid, false); - if (!_client.Connect(_networkCredential, _configurationName, false)) + + // isFirstConnection: false - specifies to use the SESSION_SERVICE_ID_2 socket. + _client = new RemoteSessionHyperVSocketClient(_vmGuid, useBackwardsCompatibleMode: useBackwardsCompatibleMode, isFirstConnection: false, authenticationToken: token); + if (!_client.Connect(_networkCredential, _configurationName, isFirstConnection: false)) { _client.Dispose(); throw new PSInvalidOperationException( @@ -1617,7 +1621,9 @@ internal ContainerHyperVSocketClientSessionTransportManager( /// </summary> public override void CreateAsync() { - _client = new RemoteSessionHyperVSocketClient(_targetGuid, false, true); + // Container scenario is not working. + // When we fix it we need to setup the token in ContainerConnectionInfo and use it here. + _client = new RemoteSessionHyperVSocketClient(_targetGuid, isFirstConnection: false, useBackwardsCompatibleMode: false, isContainer: true); if (!_client.Connect(null, string.Empty, false)) { _client.Dispose(); @@ -1716,7 +1722,7 @@ public override void CreateAsync() // Start connection timeout timer if requested. // Timer callback occurs only once after timeout time. _connectionTimer = new Timer( - callback: (_) => + callback: (_) => { if (_connectionEstablished) { @@ -1727,7 +1733,7 @@ public override void CreateAsync() bool sshTerminated = false; try { - using (var sshProcess = System.Diagnostics.Process.GetProcessById(_sshProcessId)) + using (var sshProcess = Process.GetProcessById(_sshProcessId)) { sshTerminated = sshProcess == null || sshProcess.Handle == IntPtr.Zero || sshProcess.HasExited; } @@ -1841,7 +1847,7 @@ private void ProcessErrorThread(object state) // Messages in error stream from ssh are unreliable, and may just be warnings or // banner text. // So just report the messages but don't act on them. - System.Console.WriteLine(error); + Console.WriteLine(error); } catch (IOException) { } @@ -1901,10 +1907,10 @@ private void ProcessReaderThread(object state) break; } - if (data.StartsWith(System.Management.Automation.Remoting.Server.FormattedErrorTextWriter.ErrorPrefix, StringComparison.OrdinalIgnoreCase)) + if (data.StartsWith(OutOfProcessTextWriter.ErrorPrefix, StringComparison.OrdinalIgnoreCase)) { // Error message from the server. - string errorData = data.Substring(System.Management.Automation.Remoting.Server.FormattedErrorTextWriter.ErrorPrefix.Length); + string errorData = data.Substring(OutOfProcessTextWriter.ErrorPrefix.Length); HandleErrorDataReceived(errorData); } else @@ -2505,7 +2511,7 @@ internal OutOfProcessServerSessionTransportManager(OutOfProcessTextWriter outWri _stdErrWriter = errWriter; _cmdTransportManagers = new Dictionary<Guid, OutOfProcessServerTransportManager>(); - this.WSManTransportErrorOccured += (object sender, TransportErrorOccuredEventArgs e) => + this.WSManTransportErrorOccured += (object sender, TransportErrorOccuredEventArgs e) => { string msg = e.Exception.TransportMessage ?? e.Exception.InnerException?.Message ?? string.Empty; _stdErrWriter.WriteLine(StringUtil.Format(RemotingErrorIdStrings.RemoteTransportError, msg)); diff --git a/src/System.Management.Automation/engine/remoting/fanin/PriorityCollection.cs b/src/System.Management.Automation/engine/remoting/fanin/PriorityCollection.cs index 4d8535ca4a9..a0f38a78a07 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/PriorityCollection.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/PriorityCollection.cs @@ -561,11 +561,11 @@ internal void ProcessRawData(byte[] data, OnDataAvailableCallback callback) PSEtwLog.LogAnalyticVerbose( PSEventId.ReceivedRemotingFragment, PSOpcode.Receive, PSTask.None, PSKeyword.Transport | PSKeyword.UseAlwaysAnalytic, - (Int64)objectId, - (Int64)fragmentId, + (long)objectId, + (long)fragmentId, sFlag ? 1 : 0, eFlag ? 1 : 0, - (UInt32)blobLength, + (uint)blobLength, new PSETWBinaryBlob(oneFragment, FragmentedRemoteObject.HeaderLength, blobLength)); byte[] extraData = null; diff --git a/src/System.Management.Automation/engine/remoting/fanin/WSManNativeAPI.cs b/src/System.Management.Automation/engine/remoting/fanin/WSManNativeAPI.cs index 289f88c576f..d7bce634620 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/WSManNativeAPI.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/WSManNativeAPI.cs @@ -304,7 +304,6 @@ internal struct WSManUserNameCredentialStruct /// <summary> /// Making password secure. /// </summary> - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr password; } @@ -626,7 +625,6 @@ internal class WSManBinaryOrTextDataStruct { internal int bufferLength; - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr data; } @@ -637,10 +635,8 @@ internal class WSManData_ManToUn : IDisposable { private readonly WSManDataStruct _internalData; - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private IntPtr _marshalledObject = IntPtr.Zero; - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private IntPtr _marshalledBuffer = IntPtr.Zero; /// <summary> @@ -933,7 +929,6 @@ internal struct WSManStreamIDSetStruct { internal int streamIDsCount; - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr streamIDs; } @@ -1085,7 +1080,6 @@ internal struct WSManOptionSetStruct /// <summary> /// Pointer to an array of WSManOption objects. /// </summary> - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr options; internal bool optionsMustUnderstand; @@ -1223,13 +1217,11 @@ internal struct WSManCommandArgSetInternal { internal int argsCount; - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr args; } private WSManCommandArgSetInternal _internalData; - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private MarshalledObject _data; #region Managed to Unmanaged @@ -1733,7 +1725,6 @@ internal struct WSManShellAsyncCallback // GC handle which prevents garbage collector from collecting this delegate. private GCHandle _gcHandle; - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private readonly IntPtr _asyncCallback; internal WSManShellAsyncCallback(WSManShellCompletionFunction callback) diff --git a/src/System.Management.Automation/engine/remoting/fanin/WSManPluginFacade.cs b/src/System.Management.Automation/engine/remoting/fanin/WSManPluginFacade.cs index 7fbc236a4dd..2d99a42cfb9 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/WSManPluginFacade.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/WSManPluginFacade.cs @@ -343,61 +343,51 @@ internal class WSManPluginEntryDelegatesInternal /// <summary> /// WsManPluginShutdownPluginCallbackNative. /// </summary> - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginShutdownPluginCallbackNative; /// <summary> /// WSManPluginShellCallbackNative. /// </summary> - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginShellCallbackNative; /// <summary> /// WSManPluginReleaseShellContextCallbackNative. /// </summary> - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginReleaseShellContextCallbackNative; /// <summary> /// WSManPluginCommandCallbackNative. /// </summary> - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginCommandCallbackNative; /// <summary> /// WSManPluginReleaseCommandContextCallbackNative. /// </summary> - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginReleaseCommandContextCallbackNative; /// <summary> /// WSManPluginSendCallbackNative. /// </summary> - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginSendCallbackNative; /// <summary> /// WSManPluginReceiveCallbackNative. /// </summary> - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginReceiveCallbackNative; /// <summary> /// WSManPluginSignalCallbackNative. /// </summary> - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginSignalCallbackNative; /// <summary> /// WSManPluginConnectCallbackNative. /// </summary> - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginConnectCallbackNative; /// <summary> /// WSManPluginCommandCallbackNative. /// </summary> - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] internal IntPtr wsManPluginShutdownCallbackNative; } } diff --git a/src/System.Management.Automation/engine/remoting/fanin/WSManTransportManager.cs b/src/System.Management.Automation/engine/remoting/fanin/WSManTransportManager.cs index 58b9cfe098d..d4ee779b5a2 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/WSManTransportManager.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/WSManTransportManager.cs @@ -317,18 +317,13 @@ internal CompletionEventArgs(CompletionNotification notification) #endregion #region Private Data + // operation handles are owned by WSMan - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private IntPtr _wsManSessionHandle; - - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private IntPtr _wsManShellOperationHandle; - - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private IntPtr _wsManReceiveOperationHandle; - - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private IntPtr _wsManSendOperationHandle; + // this is used with WSMan callbacks to represent a session transport manager. private long _sessionContextID; @@ -2643,7 +2638,6 @@ private void DisposeWSManAPIDataAsync() /// </summary> internal class WSManAPIDataCommon : IDisposable { - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private IntPtr _handle; // if any private WSManNativeApi.WSManStreamIDSet_ManToUn _inputStreamSet; @@ -2791,18 +2785,11 @@ internal sealed class WSManClientCommandTransportManager : BaseClientCommandTran // operation handles private readonly IntPtr _wsManShellOperationHandle; - - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private IntPtr _wsManCmdOperationHandle; - - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private IntPtr _cmdSignalOperationHandle; - - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private IntPtr _wsManReceiveOperationHandle; - - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private IntPtr _wsManSendOperationHandle; + // this is used with WSMan callbacks to represent a command transport manager. private long _cmdContextId; diff --git a/src/System.Management.Automation/engine/remoting/server/OutOfProcServerMediator.cs b/src/System.Management.Automation/engine/remoting/server/OutOfProcServerMediator.cs index 14b0240858b..6c794e21b24 100644 --- a/src/System.Management.Automation/engine/remoting/server/OutOfProcServerMediator.cs +++ b/src/System.Management.Automation/engine/remoting/server/OutOfProcServerMediator.cs @@ -635,6 +635,16 @@ private HyperVSocketMediator() originalStdErr = new HyperVSocketErrorTextWriter(_hypervSocketServer.TextWriter); } + private HyperVSocketMediator(string token, + DateTimeOffset tokenCreationTime) + : base(false) + { + _hypervSocketServer = new RemoteSessionHyperVSocketServer(false, token: token, tokenCreationTime: tokenCreationTime); + + originalStdIn = _hypervSocketServer.TextReader; + originalStdOut = new OutOfProcessTextWriter(_hypervSocketServer.TextWriter); + originalStdErr = new HyperVSocketErrorTextWriter(_hypervSocketServer.TextWriter); + } #endregion #region Static Methods @@ -656,6 +666,24 @@ internal static void Run( configurationFile: null); } + internal static void Run( + string initialCommand, + string configurationName, + string token, + DateTimeOffset tokenCreationTime) + { + lock (SyncObject) + { + s_instance = new HyperVSocketMediator(token, tokenCreationTime); + } + + s_instance.Start( + initialCommand: initialCommand, + cryptoHelper: new PSRemotingCryptoHelperServer(), + workingDirectory: null, + configurationName: configurationName, + configurationFile: null); + } #endregion } diff --git a/src/System.Management.Automation/engine/remoting/server/ServerRemoteHost.cs b/src/System.Management.Automation/engine/remoting/server/ServerRemoteHost.cs index 7e7aa767d5c..71dda0d0b7a 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerRemoteHost.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerRemoteHost.cs @@ -327,7 +327,7 @@ public override void PushRunspace(Runspace runspace) throw new PSInvalidOperationException(RemotingErrorIdStrings.ServerDriverRemoteHostAlreadyPushed); } - if (!(runspace is RemoteRunspace remoteRunspace)) + if (runspace is not RemoteRunspace remoteRunspace) { throw new PSInvalidOperationException(RemotingErrorIdStrings.ServerDriverRemoteHostNotRemoteRunspace); } diff --git a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs index 5913c98829b..027c4886380 100644 --- a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs +++ b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs @@ -7201,6 +7201,13 @@ internal static Expression InvokeMethod(MethodBase mi, DynamicMetaObject target, var argValue = parameters[i].DefaultValue; if (argValue == null) { + if (parameterType.IsByRef) + { + // When the default value is null for a ByRef parameter (e.g. an optional `in` parameter + // using `default`), expression trees cannot create Expression.Default for the T& type. + // In that case we switch to the element type and use Default(TElement) instead. + parameterType = parameterType.GetElementType(); + } argExprs[i] = Expression.Default(parameterType); } else if (!parameters[i].HasDefaultValue && parameterType != typeof(object) && argValue == Type.Missing) diff --git a/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs b/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs index 2569dd3a492..f4829bc3547 100644 --- a/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs +++ b/src/System.Management.Automation/engine/runtime/CompiledScriptBlock.cs @@ -196,7 +196,7 @@ private void ReallyCompile(bool optimize) private void PerformSecurityChecks() { - if (!(Ast is ScriptBlockAst scriptBlockAst)) + if (Ast is not ScriptBlockAst scriptBlockAst) { // Checks are only needed at the top level. return; @@ -266,12 +266,12 @@ bool IsScriptBlockInFactASafeHashtable() return false; } - if (!(endBlock.Statements[0] is PipelineAst pipelineAst)) + if (endBlock.Statements[0] is not PipelineAst pipelineAst) { return false; } - if (!(pipelineAst.GetPureExpression() is HashtableAst hashtableAst)) + if (pipelineAst.GetPureExpression() is not HashtableAst hashtableAst) { return false; } @@ -769,7 +769,7 @@ private PipelineAst GetSimplePipeline(Func<string, PipelineAst> errorHandler) return errorHandler(AutomationExceptions.CantConvertScriptBlockWithTrap); } - if (!(statements[0] is PipelineAst pipeAst)) + if (statements[0] is not PipelineAst pipeAst) { return errorHandler(AutomationExceptions.CanOnlyConvertOnePipeline); } diff --git a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs index ddc70fabd50..d584666ab62 100644 --- a/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs +++ b/src/System.Management.Automation/engine/runtime/Operations/MiscOps.cs @@ -1092,14 +1092,11 @@ internal override void Bind(PipelineProcessor pipelineProcessor, CommandProcesso { // Check first to see if File is a variable path. If so, we'll not create the FileBytePipe bool redirectToVariable = false; - if (ExperimentalFeature.IsEnabled(ExperimentalFeature.PSRedirectToVariable)) + + context.SessionState.Path.GetUnresolvedProviderPathFromPSPath(File, out ProviderInfo p, out _); + if (p != null && p.NameEquals(context.ProviderNames.Variable)) { - ProviderInfo p; - context.SessionState.Path.GetUnresolvedProviderPathFromPSPath(File, out p, out _); - if (p != null && p.NameEquals(context.ProviderNames.Variable)) - { - redirectToVariable = true; - } + redirectToVariable = true; } if (commandProcessor is NativeCommandProcessor nativeCommand @@ -1223,12 +1220,10 @@ internal Pipe GetRedirectionPipe(ExecutionContext context, PipelineProcessor par // determine whether we're trying to set a variable by inspecting the file path // if we can determine that it's a variable, we'll use Set-Variable rather than Out-File - ProviderInfo p; - PSDriveInfo d; CommandProcessorBase commandProcessor; - var name = context.SessionState.Path.GetUnresolvedProviderPathFromPSPath(File, out p, out d); + var name = context.SessionState.Path.GetUnresolvedProviderPathFromPSPath(File, out ProviderInfo p, out _); - if (ExperimentalFeature.IsEnabled(ExperimentalFeature.PSRedirectToVariable) && p != null && p.NameEquals(context.ProviderNames.Variable)) + if (p != null && p.NameEquals(context.ProviderNames.Variable)) { commandProcessor = context.CreateCommand("Set-Variable", false); Diagnostics.Assert(commandProcessor != null, "CreateCommand returned null"); @@ -3649,6 +3644,7 @@ internal static object[] GetSlice(IList list, int startIndex) internal static class MemberInvocationLoggingOps { +#if DEBUG private static readonly Lazy<bool> DumpLogAMSIContent = new Lazy<bool>( () => { object result = Environment.GetEnvironmentVariable("__PSDumpAMSILogContent"); @@ -3659,6 +3655,7 @@ internal static class MemberInvocationLoggingOps return false; } ); +#endif private static string ArgumentToString(object arg) { @@ -3713,20 +3710,24 @@ internal static void LogMemberInvocation(string targetName, string name, object[ string content = $"<{targetName}>.{name}({argsBuilder})"; +#if DEBUG if (DumpLogAMSIContent.Value) { Console.WriteLine("\n=== Amsi notification report content ==="); Console.WriteLine(content); } +#endif var success = AmsiUtils.ReportContent( name: contentName, content: content); +#if DEBUG if (DumpLogAMSIContent.Value) { Console.WriteLine($"=== Amsi notification report success: {success} ==="); } +#endif } catch (PSSecurityException) { @@ -3734,12 +3735,16 @@ internal static void LogMemberInvocation(string targetName, string name, object[ // must be propagated. throw; } +#pragma warning disable CS0168 // variable declared but never used catch (Exception ex) +#pragma warning restore CS0168 { +#if DEBUG if (DumpLogAMSIContent.Value) { Console.WriteLine($"!!! Amsi notification report exception: {ex} !!!"); } +#endif } } } diff --git a/src/System.Management.Automation/engine/runtime/ScriptBlockToPowerShell.cs b/src/System.Management.Automation/engine/runtime/ScriptBlockToPowerShell.cs index b0017080571..defd87662e8 100644 --- a/src/System.Management.Automation/engine/runtime/ScriptBlockToPowerShell.cs +++ b/src/System.Management.Automation/engine/runtime/ScriptBlockToPowerShell.cs @@ -327,8 +327,8 @@ internal static Dictionary<string, object> GetUsingValuesForEachParallel( bool isTrustedInput, ExecutionContext context) { - // Using variables for Foreach-Object -Parallel use are restricted to be within the - // Foreach-Object -Parallel call scope. This will filter the using variable map to variables + // Using variables for Foreach-Object -Parallel use are restricted to be within the + // Foreach-Object -Parallel call scope. This will filter the using variable map to variables // only within the current (outer) Foreach-Object -Parallel call scope. var usingAsts = UsingExpressionAstSearcher.FindAllUsingExpressions(scriptBlock.Ast).ToList(); UsingExpressionAst usingAst = null; @@ -358,11 +358,11 @@ internal static Dictionary<string, object> GetUsingValuesForEachParallel( if (rte.ErrorRecord.FullyQualifiedErrorId.Equals("VariableIsUndefined", StringComparison.Ordinal)) { throw InterpreterError.NewInterpreterException( - targetObject: null, + targetObject: null, exceptionType: typeof(RuntimeException), - errorPosition: usingAst.Extent, + errorPosition: usingAst.Extent, resourceIdAndErrorId: "UsingVariableIsUndefined", - resourceString: AutomationExceptions.UsingVariableIsUndefined, + resourceString: AutomationExceptions.UsingVariableIsUndefined, args: rte.ErrorRecord.TargetObject); } } @@ -435,7 +435,7 @@ private static bool IsInForeachParallelCallingScope( /* Example: $Test1 = "Hello" - 1 | ForEach-Object -Parallel { + 1 | ForEach-Object -Parallel { $using:Test1 $Test2 = "Goodbye" 1 | ForEach-Object -Parallel { @@ -450,7 +450,7 @@ private static bool IsInForeachParallelCallingScope( while (currentParent != scriptblockAst) { // Look for Foreach-Object outer commands - if (currentParent is CommandAst commandAst && + if (currentParent is CommandAst commandAst && FindForEachInCommand(commandAst)) { // Using Ast is outside the invoking foreach scope. @@ -542,7 +542,7 @@ private static Tuple<Dictionary<string, object>, object[]> GetUsingValues( if (variables != null) { - if (!(usingAst.SubExpression is VariableExpressionAst variableAst)) + if (usingAst.SubExpression is not VariableExpressionAst variableAst) { throw InterpreterError.NewInterpreterException(null, typeof(RuntimeException), usingAst.Extent, "CantGetUsingExpressionValueWithSpecifiedVariableDictionary", AutomationExceptions.CantGetUsingExpressionValueWithSpecifiedVariableDictionary, usingAst.Extent.Text); diff --git a/src/System.Management.Automation/engine/serialization.cs b/src/System.Management.Automation/engine/serialization.cs index 3cffd998d49..add0eab25dc 100644 --- a/src/System.Management.Automation/engine/serialization.cs +++ b/src/System.Management.Automation/engine/serialization.cs @@ -2018,7 +2018,7 @@ int depth foreach (PSMemberInfo info in propertyCollection) { - if (!(info is PSProperty prop)) + if (info is not PSProperty prop) { continue; } @@ -3373,28 +3373,28 @@ private CimClass RehydrateCimClass(PSPropertyInfo classMetadataProperty) PSObject psoDeserializedClass = PSObject.AsPSObject(deserializedClass); - if (!(psoDeserializedClass.InstanceMembers[InternalDeserializer.CimNamespaceProperty] is PSPropertyInfo namespaceProperty)) + if (psoDeserializedClass.InstanceMembers[InternalDeserializer.CimNamespaceProperty] is not PSPropertyInfo namespaceProperty) { return null; } string cimNamespace = namespaceProperty.Value as string; - if (!(psoDeserializedClass.InstanceMembers[InternalDeserializer.CimClassNameProperty] is PSPropertyInfo classNameProperty)) + if (psoDeserializedClass.InstanceMembers[InternalDeserializer.CimClassNameProperty] is not PSPropertyInfo classNameProperty) { return null; } string cimClassName = classNameProperty.Value as string; - if (!(psoDeserializedClass.InstanceMembers[InternalDeserializer.CimServerNameProperty] is PSPropertyInfo computerNameProperty)) + if (psoDeserializedClass.InstanceMembers[InternalDeserializer.CimServerNameProperty] is not PSPropertyInfo computerNameProperty) { return null; } string computerName = computerNameProperty.Value as string; - if (!(psoDeserializedClass.InstanceMembers[InternalDeserializer.CimHashCodeProperty] is PSPropertyInfo hashCodeProperty)) + if (psoDeserializedClass.InstanceMembers[InternalDeserializer.CimHashCodeProperty] is not PSPropertyInfo hashCodeProperty) { return null; } @@ -3511,7 +3511,7 @@ private PSObject RehydrateCimInstance(PSObject deserializedObject) { foreach (PSMemberInfo deserializedMemberInfo in deserializedObject.AdaptedMembers) { - if (!(deserializedMemberInfo is PSPropertyInfo deserializedProperty)) + if (deserializedMemberInfo is not PSPropertyInfo deserializedProperty) { continue; } @@ -3531,7 +3531,7 @@ private PSObject RehydrateCimInstance(PSObject deserializedObject) // process properties that were originally "extended" properties foreach (PSMemberInfo deserializedMemberInfo in deserializedObject.InstanceMembers) { - if (!(deserializedMemberInfo is PSPropertyInfo deserializedProperty)) + if (deserializedMemberInfo is not PSPropertyInfo deserializedProperty) { continue; } @@ -7345,7 +7345,7 @@ public static UInt32 GetParameterSetMetadataFlags(PSObject instance) throw PSTraceSource.NewArgumentNullException(nameof(instance)); } - if (!(instance.BaseObject is ParameterSetMetadata parameterSetMetadata)) + if (instance.BaseObject is not ParameterSetMetadata parameterSetMetadata) { throw PSTraceSource.NewArgumentNullException(nameof(instance)); } @@ -7366,7 +7366,7 @@ public static PSObject GetInvocationInfo(PSObject instance) throw PSTraceSource.NewArgumentNullException(nameof(instance)); } - if (!(instance.BaseObject is DebuggerStopEventArgs dbgStopEventArgs)) + if (instance.BaseObject is not DebuggerStopEventArgs dbgStopEventArgs) { throw PSTraceSource.NewArgumentNullException(nameof(instance)); } @@ -7625,7 +7625,7 @@ public static Guid GetFormatViewDefinitionInstanceId(PSObject instance) throw PSTraceSource.NewArgumentNullException(nameof(instance)); } - if (!(instance.BaseObject is FormatViewDefinition formatViewDefinition)) + if (instance.BaseObject is not FormatViewDefinition formatViewDefinition) { throw PSTraceSource.NewArgumentNullException(nameof(instance)); } diff --git a/src/System.Management.Automation/help/HelpCommentsParser.cs b/src/System.Management.Automation/help/HelpCommentsParser.cs index 36d0cb6450e..b4a21e04d69 100644 --- a/src/System.Management.Automation/help/HelpCommentsParser.cs +++ b/src/System.Management.Automation/help/HelpCommentsParser.cs @@ -951,7 +951,7 @@ internal static bool IsCommentHelpText(List<Token> commentBlock) var result = new List<Language.Token>(); // Any whitespace between the token and the first comment is allowed. - int nextMaxStartLine = Int32.MaxValue; + int nextMaxStartLine = int.MaxValue; for (int i = startIndex; i < tokens.Length; i++) { diff --git a/src/System.Management.Automation/help/UpdatableHelpSystem.cs b/src/System.Management.Automation/help/UpdatableHelpSystem.cs index 6ab8d469a97..14edabf9613 100644 --- a/src/System.Management.Automation/help/UpdatableHelpSystem.cs +++ b/src/System.Management.Automation/help/UpdatableHelpSystem.cs @@ -419,6 +419,7 @@ private string ResolveUri(string baseUri, bool verbose) using (HttpClient client = new HttpClient(handler)) { client.Timeout = new TimeSpan(0, 0, 30); // Set 30 second timeout + // codeql[cs/ssrf] - This is expected Poweshell behavior and the user assumes trust for the module they download and any URIs it references. The URIs are also not executables or scripts that would be invoked by this method. Task<HttpResponseMessage> responseMessage = client.GetAsync(uri); using (HttpResponseMessage response = responseMessage.Result) { @@ -783,6 +784,7 @@ private bool DownloadHelpContentHttpClient(string uri, string fileName, Updatabl using (HttpClient client = new HttpClient(handler)) { client.Timeout = _defaultTimeout; + // codeql[cs/ssrf] - This is expected Poweshell behavior and the user assumes trust for the module they download and any URIs it references. The URIs are also not executables or scripts that would be invoked by this method. Task<HttpResponseMessage> responseMsg = client.GetAsync(new Uri(uri), _cancelTokenSource.Token); // TODO: Should I use a continuation to write the stream to a file? diff --git a/src/System.Management.Automation/namespaces/ContainerProviderBase.cs b/src/System.Management.Automation/namespaces/ContainerProviderBase.cs index cc3e0ac83e2..2a600097108 100644 --- a/src/System.Management.Automation/namespaces/ContainerProviderBase.cs +++ b/src/System.Management.Automation/namespaces/ContainerProviderBase.cs @@ -840,7 +840,7 @@ protected virtual object RenameItemDynamicParameters(string path, string newName /// /// The <paramref name="newItemValue"/> parameter can be any type of object that the provider can use /// to create the item. It is recommended that the provider accept at a minimum strings, and an instance - /// of the type of object that would be returned from GetItem() for this path. <see cref="LanguagePrimitives.ConvertTo(System.Object, System.Type)"/> + /// of the type of object that would be returned from GetItem() for this path. <see cref="LanguagePrimitives.ConvertTo(object, System.Type)"/> /// can be used to convert some types to the desired type. /// /// The default implementation of this method throws an <see cref="System.Management.Automation.PSNotSupportedException"/>. diff --git a/src/System.Management.Automation/namespaces/FileSystemContentStream.cs b/src/System.Management.Automation/namespaces/FileSystemContentStream.cs index d95b31ed0fd..d0f8f08deab 100644 --- a/src/System.Management.Automation/namespaces/FileSystemContentStream.cs +++ b/src/System.Management.Automation/namespaces/FileSystemContentStream.cs @@ -1475,26 +1475,6 @@ _currentEncoding is UTF32Encoding || return _byteCount; } - - private static class NativeMethods - { - // Default values - private const int MAX_DEFAULTCHAR = 2; - private const int MAX_LEADBYTES = 12; - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - internal struct CPINFO - { - [MarshalAs(UnmanagedType.U4)] - internal int MaxCharSize; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_DEFAULTCHAR)] - public byte[] DefaultChar; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_LEADBYTES)] - public byte[] LeadBytes; - } - } } /// <summary> diff --git a/src/System.Management.Automation/namespaces/FileSystemProvider.cs b/src/System.Management.Automation/namespaces/FileSystemProvider.cs index 3d13ac05d4d..05c8fff76e0 100644 --- a/src/System.Management.Automation/namespaces/FileSystemProvider.cs +++ b/src/System.Management.Automation/namespaces/FileSystemProvider.cs @@ -569,7 +569,7 @@ protected override PSDriveInfo NewDrive(PSDriveInfo drive) if (driveIsFixed) { // Since the drive is fixed, ensure the root is valid. - validDrive = Directory.Exists(drive.Root); + validDrive = SafeDoesPathExist(drive.Root); } if (validDrive) @@ -908,7 +908,7 @@ protected override Collection<PSDriveInfo> InitializeDefaultDrives() if (newDrive.DriveType == DriveType.Fixed) { - if (!newDrive.RootDirectory.Exists) + if (!SafeDoesPathExist(newDrive.RootDirectory.FullName)) { continue; } @@ -1227,6 +1227,29 @@ protected override void GetItem(string path) } } + private static bool SafeDoesPathExist(string rootDirectory) + { + if (Directory.Exists(rootDirectory)) + { + return true; + } + + try + { + return (File.GetAttributes(rootDirectory) & FileAttributes.Directory) is not 0; + } + // In some scenarios (like AppContainers) direct access to the root directory may + // be prevented, but more specific paths may be accessible. + catch (UnauthorizedAccessException) + { + return true; + } + catch + { + return false; + } + } + private FileSystemInfo GetFileSystemItem(string path, ref bool isContainer, bool showHidden) { path = NormalizePath(path); @@ -1324,6 +1347,7 @@ protected override void InvokeDefaultAction(string path) if (ShouldProcess(resource, action)) { var invokeProcess = new System.Diagnostics.Process(); + // codeql[cs/microsoft/command-line-injection-shell-execution] - This is expected Poweshell behavior where user inputted paths are supported for the context of this method. The user assumes trust for the file path they are specifying. If there is concern for remoting, restricted remoting guidelines should be used. invokeProcess.StartInfo.FileName = path; #if UNIX bool useShellExecute = false; @@ -1902,15 +1926,16 @@ string ToModeString(FileSystemInfo fileSystemInfo) } } - bool isDirectory = fileAttributes.HasFlag(FileAttributes.Directory); - ReadOnlySpan<char> mode = stackalloc char[] - { - isLink ? 'l' : isDirectory ? 'd' : '-', + ReadOnlySpan<char> mode = + [ + isLink ? + 'l' : + fileAttributes.HasFlag(FileAttributes.Directory) ? 'd' : '-', fileAttributes.HasFlag(FileAttributes.Archive) ? 'a' : '-', fileAttributes.HasFlag(FileAttributes.ReadOnly) ? 'r' : '-', fileAttributes.HasFlag(FileAttributes.Hidden) ? 'h' : '-', fileAttributes.HasFlag(FileAttributes.System) ? 's' : '-', - }; + ]; return new string(mode); } @@ -8285,7 +8310,7 @@ internal static void DeleteFileStream(string path, string streamName) ArgumentNullException.ThrowIfNull(streamName); string adjustedStreamName = streamName.Trim(); - if (adjustedStreamName.IndexOf(':') != 0) + if (!adjustedStreamName.StartsWith(':')) { adjustedStreamName = ":" + adjustedStreamName; } diff --git a/src/System.Management.Automation/namespaces/LocationGlobber.cs b/src/System.Management.Automation/namespaces/LocationGlobber.cs index 19377985c81..65c28f1586d 100644 --- a/src/System.Management.Automation/namespaces/LocationGlobber.cs +++ b/src/System.Management.Automation/namespaces/LocationGlobber.cs @@ -4539,7 +4539,7 @@ internal static bool IsHomePath(string path) } } - if (path.IndexOf(StringLiterals.HomePath, StringComparison.Ordinal) == 0) + if (path.StartsWith(StringLiterals.HomePath, StringComparison.Ordinal)) { // Support the single "~" if (path.Length == 1) @@ -4638,7 +4638,7 @@ internal string GetHomeRelativePath(string path) } } - if (path.IndexOf(StringLiterals.HomePath, StringComparison.Ordinal) == 0) + if (path.StartsWith(StringLiterals.HomePath, StringComparison.Ordinal)) { // Strip of the ~ and the \ or / if present diff --git a/src/System.Management.Automation/namespaces/ProviderBase.cs b/src/System.Management.Automation/namespaces/ProviderBase.cs index ca4cb1de64c..4345ec9f8ec 100644 --- a/src/System.Management.Automation/namespaces/ProviderBase.cs +++ b/src/System.Management.Automation/namespaces/ProviderBase.cs @@ -260,7 +260,7 @@ internal void GetProperty( { Context = cmdletProviderContext; - if (!(this is IPropertyCmdletProvider propertyProvider)) + if (this is not IPropertyCmdletProvider propertyProvider) { throw PSTraceSource.NewNotSupportedException( @@ -298,7 +298,7 @@ internal object GetPropertyDynamicParameters( { Context = cmdletProviderContext; - if (!(this is IPropertyCmdletProvider propertyProvider)) + if (this is not IPropertyCmdletProvider propertyProvider) { return null; } @@ -327,7 +327,7 @@ internal void SetProperty( { Context = cmdletProviderContext; - if (!(this is IPropertyCmdletProvider propertyProvider)) + if (this is not IPropertyCmdletProvider propertyProvider) { throw PSTraceSource.NewNotSupportedException( @@ -365,7 +365,7 @@ internal object SetPropertyDynamicParameters( { Context = cmdletProviderContext; - if (!(this is IPropertyCmdletProvider propertyProvider)) + if (this is not IPropertyCmdletProvider propertyProvider) { return null; } @@ -397,7 +397,7 @@ internal void ClearProperty( { Context = cmdletProviderContext; - if (!(this is IPropertyCmdletProvider propertyProvider)) + if (this is not IPropertyCmdletProvider propertyProvider) { throw PSTraceSource.NewNotSupportedException( @@ -435,7 +435,7 @@ internal object ClearPropertyDynamicParameters( { Context = cmdletProviderContext; - if (!(this is IPropertyCmdletProvider propertyProvider)) + if (this is not IPropertyCmdletProvider propertyProvider) { return null; } @@ -479,7 +479,7 @@ internal void NewProperty( { Context = cmdletProviderContext; - if (!(this is IDynamicPropertyCmdletProvider propertyProvider)) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { throw PSTraceSource.NewNotSupportedException( @@ -524,7 +524,7 @@ internal object NewPropertyDynamicParameters( { Context = cmdletProviderContext; - if (!(this is IDynamicPropertyCmdletProvider propertyProvider)) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { return null; } @@ -556,7 +556,7 @@ internal void RemoveProperty( { Context = cmdletProviderContext; - if (!(this is IDynamicPropertyCmdletProvider propertyProvider)) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { throw PSTraceSource.NewNotSupportedException( @@ -593,7 +593,7 @@ internal object RemovePropertyDynamicParameters( { Context = cmdletProviderContext; - if (!(this is IDynamicPropertyCmdletProvider propertyProvider)) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { return null; } @@ -629,7 +629,7 @@ internal void RenameProperty( { Context = cmdletProviderContext; - if (!(this is IDynamicPropertyCmdletProvider propertyProvider)) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { throw PSTraceSource.NewNotSupportedException( @@ -670,7 +670,7 @@ internal object RenamePropertyDynamicParameters( { Context = cmdletProviderContext; - if (!(this is IDynamicPropertyCmdletProvider propertyProvider)) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { return null; } @@ -710,7 +710,7 @@ internal void CopyProperty( { Context = cmdletProviderContext; - if (!(this is IDynamicPropertyCmdletProvider propertyProvider)) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { throw PSTraceSource.NewNotSupportedException( @@ -755,7 +755,7 @@ internal object CopyPropertyDynamicParameters( { Context = cmdletProviderContext; - if (!(this is IDynamicPropertyCmdletProvider propertyProvider)) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { return null; } @@ -795,7 +795,7 @@ internal void MoveProperty( { Context = cmdletProviderContext; - if (!(this is IDynamicPropertyCmdletProvider propertyProvider)) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { throw PSTraceSource.NewNotSupportedException( @@ -840,7 +840,7 @@ internal object MovePropertyDynamicParameters( { Context = cmdletProviderContext; - if (!(this is IDynamicPropertyCmdletProvider propertyProvider)) + if (this is not IDynamicPropertyCmdletProvider propertyProvider) { return null; } @@ -871,7 +871,7 @@ internal IContentReader GetContentReader( { Context = cmdletProviderContext; - if (!(this is IContentCmdletProvider contentProvider)) + if (this is not IContentCmdletProvider contentProvider) { throw PSTraceSource.NewNotSupportedException( @@ -904,7 +904,7 @@ internal object GetContentReaderDynamicParameters( { Context = cmdletProviderContext; - if (!(this is IContentCmdletProvider contentProvider)) + if (this is not IContentCmdletProvider contentProvider) { return null; } @@ -931,7 +931,7 @@ internal IContentWriter GetContentWriter( { Context = cmdletProviderContext; - if (!(this is IContentCmdletProvider contentProvider)) + if (this is not IContentCmdletProvider contentProvider) { throw PSTraceSource.NewNotSupportedException( @@ -964,7 +964,7 @@ internal object GetContentWriterDynamicParameters( { Context = cmdletProviderContext; - if (!(this is IContentCmdletProvider contentProvider)) + if (this is not IContentCmdletProvider contentProvider) { return null; } @@ -988,7 +988,7 @@ internal void ClearContent( { Context = cmdletProviderContext; - if (!(this is IContentCmdletProvider contentProvider)) + if (this is not IContentCmdletProvider contentProvider) { throw PSTraceSource.NewNotSupportedException( @@ -1021,7 +1021,7 @@ internal object ClearContentDynamicParameters( { Context = cmdletProviderContext; - if (!(this is IContentCmdletProvider contentProvider)) + if (this is not IContentCmdletProvider contentProvider) { return null; } @@ -1984,4 +1984,3 @@ public void WriteError(ErrorRecord errorRecord) } #pragma warning restore 56506 - diff --git a/src/System.Management.Automation/namespaces/RegistryProvider.cs b/src/System.Management.Automation/namespaces/RegistryProvider.cs index db901160910..4e3b84716cd 100644 --- a/src/System.Management.Automation/namespaces/RegistryProvider.cs +++ b/src/System.Management.Automation/namespaces/RegistryProvider.cs @@ -1835,7 +1835,7 @@ public void GetProperty( WriteError(new ErrorRecord( invalidCast, invalidCast.GetType().FullName, - ErrorCategory.ReadError, + ErrorCategory.ReadError, path)); } } diff --git a/src/System.Management.Automation/namespaces/TransactedRegistry.cs b/src/System.Management.Automation/namespaces/TransactedRegistry.cs index 56062e6de6c..6465ea1819c 100644 --- a/src/System.Management.Automation/namespaces/TransactedRegistry.cs +++ b/src/System.Management.Automation/namespaces/TransactedRegistry.cs @@ -42,7 +42,6 @@ internal static class TransactedRegistry /// subkeys, there must be a Transaction.Current and the resulting TransactedRegistryKey from those operations ARE associated with /// the transaction.</para> /// </summary> - [ResourceExposure(ResourceScope.Machine)] // The TransactedRegistryKey's members cannot be changed. [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] internal static readonly TransactedRegistryKey CurrentUser = TransactedRegistryKey.GetBaseKey(BaseRegistryKeys.HKEY_CURRENT_USER); @@ -61,7 +60,6 @@ internal static class TransactedRegistry /// subkeys, there must be a Transaction.Current and the resulting TransactedRegistryKey from those operations ARE associated with /// the transaction.</para> /// </summary> - [ResourceExposure(ResourceScope.Machine)] // The TransactedRegistryKey's members cannot be changed. [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] internal static readonly TransactedRegistryKey LocalMachine = TransactedRegistryKey.GetBaseKey(BaseRegistryKeys.HKEY_LOCAL_MACHINE); @@ -80,7 +78,6 @@ internal static class TransactedRegistry /// subkeys, there must be a Transaction.Current and the resulting TransactedRegistryKey from those operations ARE associated with /// the transaction.</para> /// </summary> - [ResourceExposure(ResourceScope.Machine)] // The TransactedRegistryKey's members cannot be changed. [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] internal static readonly TransactedRegistryKey ClassesRoot = TransactedRegistryKey.GetBaseKey(BaseRegistryKeys.HKEY_CLASSES_ROOT); @@ -99,7 +96,6 @@ internal static class TransactedRegistry /// subkeys, there must be a Transaction.Current and the resulting TransactedRegistryKey from those operations ARE associated with /// the transaction.</para> /// </summary> - [ResourceExposure(ResourceScope.Machine)] // The TransactedRegistryKey's members cannot be changed. [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] internal static readonly TransactedRegistryKey Users = TransactedRegistryKey.GetBaseKey(BaseRegistryKeys.HKEY_USERS); @@ -118,7 +114,6 @@ internal static class TransactedRegistry /// subkeys, there must be a Transaction.Current and the resulting TransactedRegistryKey from those operations ARE associated with /// the transaction.</para> /// </summary> - [ResourceExposure(ResourceScope.Machine)] // The TransactedRegistryKey's members cannot be changed. [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] internal static readonly TransactedRegistryKey CurrentConfig = TransactedRegistryKey.GetBaseKey(BaseRegistryKeys.HKEY_CURRENT_CONFIG); diff --git a/src/System.Management.Automation/namespaces/TransactedRegistryKey.cs b/src/System.Management.Automation/namespaces/TransactedRegistryKey.cs index 23316a31724..d899d9257b4 100644 --- a/src/System.Management.Automation/namespaces/TransactedRegistryKey.cs +++ b/src/System.Management.Automation/namespaces/TransactedRegistryKey.cs @@ -141,9 +141,6 @@ public sealed class TransactedRegistryKey : MarshalByRefObject, IDisposable // If that call fails with ERROR_INVALID_TRANSACTION, we have possibly run into bug 181242. To workaround // this, we open the key without a transaction and then open it again with // a transaction and return THAT hkey. - - // Suppressed because there is no way for arbitrary data to be passed. - [SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage")] private int RegOpenKeyTransactedWrapper(SafeRegistryHandle hKey, string lpSubKey, int ulOptions, int samDesired, out SafeRegistryHandle hkResult, SafeTransactionHandle hTransaction, IntPtr pExtendedParameter) @@ -346,8 +343,6 @@ public void Dispose() /// otherwise an ArgumentException is thrown.</param> /// <returns>A TransactedRegistryKey object for the subkey, which is associated with Transaction.Current. /// returns null if the operation failed.</returns> - [ResourceExposure(ResourceScope.Machine)] - [ResourceConsumption(ResourceScope.Machine)] // Suppressed to be consistent with naming in Microsoft.Win32.RegistryKey [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] public TransactedRegistryKey CreateSubKey(string subkey) @@ -366,8 +361,6 @@ public TransactedRegistryKey CreateSubKey(string subkey) /// <param name='permissionCheck'>One of the Microsoft.Win32.RegistryKeyPermissionCheck values that /// specifies whether the key is opened for read or read/write access.</param> [ComVisible(false)] - [ResourceExposure(ResourceScope.Machine)] - [ResourceConsumption(ResourceScope.Machine)] // Suppressed to be consistent with naming in Microsoft.Win32.RegistryKey [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] public TransactedRegistryKey CreateSubKey(string subkey, RegistryKeyPermissionCheck permissionCheck) @@ -387,8 +380,6 @@ public TransactedRegistryKey CreateSubKey(string subkey, RegistryKeyPermissionCh /// specifies whether the key is opened for read or read/write access.</param> /// <param name='registrySecurity'>A TransactedRegistrySecurity object that specifies the access control security for the new key.</param> [ComVisible(false)] - [ResourceExposure(ResourceScope.Machine)] - [ResourceConsumption(ResourceScope.Machine)] // Suppressed to be consistent with naming in Microsoft.Win32.RegistryKey [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] public unsafe TransactedRegistryKey CreateSubKey(string subkey, RegistryKeyPermissionCheck permissionCheck, TransactedRegistrySecurity registrySecurity) @@ -397,8 +388,6 @@ public unsafe TransactedRegistryKey CreateSubKey(string subkey, RegistryKeyPermi } [ComVisible(false)] - [ResourceExposure(ResourceScope.Machine)] - [ResourceConsumption(ResourceScope.Machine)] // Suppressed to be consistent with naming in Microsoft.Win32.RegistryKey [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] private unsafe TransactedRegistryKey CreateSubKeyInternal(string subkey, RegistryKeyPermissionCheck permissionCheck, object registrySecurityObj) @@ -489,8 +478,6 @@ private unsafe TransactedRegistryKey CreateSubKeyInternal(string subkey, Registr /// <exception cref="InvalidOperationException">Thrown if the subkey as child subkeys.</exception> /// </summary> /// <param name='subkey'>The subkey to delete.</param> - [ResourceExposure(ResourceScope.Machine)] - [ResourceConsumption(ResourceScope.Machine)] // Suppressed to be consistent with naming in Microsoft.Win32.RegistryKey [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] public void DeleteSubKey(string subkey) @@ -510,8 +497,6 @@ public void DeleteSubKey(string subkey) /// <param name='throwOnMissingSubKey'>Specify true if an ArgumentException should be thrown if /// the specified subkey does not exist. If false is specified, a missing subkey does not throw /// an exception.</param> - [ResourceExposure(ResourceScope.Machine)] - [ResourceConsumption(ResourceScope.Machine)] // Suppressed to be consistent with naming in Microsoft.Win32.RegistryKey [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] public void DeleteSubKey(string subkey, bool throwOnMissingSubKey) @@ -569,8 +554,6 @@ public void DeleteSubKey(string subkey, bool throwOnMissingSubKey) /// Utilizes Transaction.Current for its transaction.</para> /// </summary> /// <param name="subkey">The subkey to delete.</param> - [ResourceExposure(ResourceScope.Machine)] - [ResourceConsumption(ResourceScope.Machine)] // Suppressed to be consistent with naming in Microsoft.Win32.RegistryKey [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] public void DeleteSubKeyTree(string subkey) @@ -626,8 +609,6 @@ public void DeleteSubKeyTree(string subkey) // An internal version which does no security checks or argument checking. Skipping the // security checks should give us a slight perf gain on large trees. - [ResourceExposure(ResourceScope.Machine)] - [ResourceConsumption(ResourceScope.Machine)] // Suppressed to be consistent with naming in Microsoft.Win32.RegistryKey [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] private void DeleteSubKeyTreeInternal(string subkey) @@ -672,8 +653,6 @@ private void DeleteSubKeyTreeInternal(string subkey) /// Utilizes Transaction.Current for its transaction.</para> /// </summary> /// <param name="name">Name of the value to delete.</param> - [ResourceExposure(ResourceScope.None)] - [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] public void DeleteValue(string name) { DeleteValue(name, true); @@ -687,8 +666,6 @@ public void DeleteValue(string name) /// <param name="throwOnMissingValue">Specify true if an ArgumentException should be thrown if /// the specified value does not exist. If false is specified, a missing value does not throw /// an exception.</param> - [ResourceExposure(ResourceScope.None)] - [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] public void DeleteValue(string name, bool throwOnMissingValue) { EnsureWriteable(); @@ -758,8 +735,6 @@ internal static TransactedRegistryKey GetBaseKey(IntPtr hKey) /// <returns>The subkey requested or null if the operation failed.</returns> /// <param name="name">Name or path of the subkey to open.</param> /// <param name="writable">Set to true of you only need readonly access.</param> - [ResourceExposure(ResourceScope.Machine)] - [ResourceConsumption(ResourceScope.Machine)] // Suppressed to be consistent with naming in Microsoft.Win32.RegistryKey [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] public TransactedRegistryKey OpenSubKey(string name, bool writable) @@ -803,8 +778,6 @@ public TransactedRegistryKey OpenSubKey(string name, bool writable) /// <param name="permissionCheck">One of the Microsoft.Win32.RegistryKeyPermissionCheck values that specifies /// whether the key is opened for read or read/write access.</param> [ComVisible(false)] - [ResourceExposure(ResourceScope.Machine)] - [ResourceConsumption(ResourceScope.Machine)] // Suppressed to be consistent with naming in Microsoft.Win32.RegistryKey [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] public TransactedRegistryKey OpenSubKey(string name, RegistryKeyPermissionCheck permissionCheck) @@ -823,8 +796,6 @@ public TransactedRegistryKey OpenSubKey(string name, RegistryKeyPermissionCheck /// whether the key is opened for read or read/write access.</param> /// <param name="rights">A bitwise combination of Microsoft.Win32.RegistryRights values that specifies the desired security access.</param> [ComVisible(false)] - [ResourceExposure(ResourceScope.Machine)] - [ResourceConsumption(ResourceScope.Machine)] // Suppressed to be consistent with naming in Microsoft.Win32.RegistryKey [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] public TransactedRegistryKey OpenSubKey(string name, RegistryKeyPermissionCheck permissionCheck, RegistryRights rights) @@ -832,8 +803,6 @@ public TransactedRegistryKey OpenSubKey(string name, RegistryKeyPermissionCheck return InternalOpenSubKey(name, permissionCheck, (int)rights); } - [ResourceExposure(ResourceScope.Machine)] - [ResourceConsumption(ResourceScope.Machine)] // Suppressed to be consistent with naming in Microsoft.Win32.RegistryKey [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] private TransactedRegistryKey InternalOpenSubKey(string name, RegistryKeyPermissionCheck permissionCheck, int rights) @@ -874,8 +843,6 @@ private TransactedRegistryKey InternalOpenSubKey(string name, RegistryKeyPermiss // This required no security checks. This is to get around the Deleting SubKeys which only require // write permission. They call OpenSubKey which required read. Now instead call this function w/o security checks - [ResourceExposure(ResourceScope.Machine)] - [ResourceConsumption(ResourceScope.Machine)] // Suppressed to be consistent with naming in Microsoft.Win32.RegistryKey [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] internal TransactedRegistryKey InternalOpenSubKey(string name, bool writable) @@ -906,8 +873,6 @@ internal TransactedRegistryKey InternalOpenSubKey(string name, bool writable) /// </summary> /// <returns>The subkey requested or null if the operation failed.</returns> /// <param name="name">Name or path of the subkey to open.</param> - [ResourceExposure(ResourceScope.Machine)] - [ResourceConsumption(ResourceScope.Machine)] // Suppressed to be consistent with naming in Microsoft.Win32.RegistryKey [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly")] public TransactedRegistryKey OpenSubKey(string name) diff --git a/src/System.Management.Automation/namespaces/Win32Native.cs b/src/System.Management.Automation/namespaces/Win32Native.cs index b785cd5fbb8..4f2c65c49f5 100644 --- a/src/System.Management.Automation/namespaces/Win32Native.cs +++ b/src/System.Management.Automation/namespaces/Win32Native.cs @@ -76,14 +76,14 @@ internal enum SID_NAME_USE #region Struct - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct SID_AND_ATTRIBUTES { internal IntPtr Sid; internal uint Attributes; } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct TOKEN_USER { internal SID_AND_ATTRIBUTES User; @@ -106,8 +106,6 @@ internal struct TOKEN_USER /// <param name="peUse"></param> /// <returns></returns> [DllImport(PinvokeDllNames.LookupAccountSidDllName, CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false)] - [ResourceExposure(ResourceScope.Machine)] - [SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage")] [return: MarshalAs(UnmanagedType.Bool)] private static extern unsafe bool LookupAccountSid(string lpSystemName, IntPtr sid, @@ -139,8 +137,6 @@ internal static unsafe bool LookupAccountSid(string lpSystemName, } [DllImport(PinvokeDllNames.CloseHandleDllName, SetLastError = true)] - [ResourceExposure(ResourceScope.Machine)] - [SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage")] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool CloseHandle(IntPtr handle); @@ -152,8 +148,6 @@ internal static unsafe bool LookupAccountSid(string lpSystemName, /// <param name="tokenHandle">Process token.</param> /// <returns>The current process token.</returns> [DllImport(PinvokeDllNames.OpenProcessTokenDllName, CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false)] - [ResourceExposure(ResourceScope.Machine)] - [SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage")] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool OpenProcessToken(IntPtr processHandle, uint desiredAccess, out IntPtr tokenHandle); @@ -168,8 +162,6 @@ internal static unsafe bool LookupAccountSid(string lpSystemName, /// <param name="returnLength"></param> /// <returns></returns> [DllImport(PinvokeDllNames.GetTokenInformationDllName, CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false)] - [ResourceExposure(ResourceScope.Machine)] - [SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage")] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool GetTokenInformation(IntPtr tokenHandle, TOKEN_INFORMATION_CLASS tokenInformationClass, diff --git a/src/System.Management.Automation/resources/HelpErrors.resx b/src/System.Management.Automation/resources/HelpErrors.resx index 27634de2995..ae797e8af12 100644 --- a/src/System.Management.Automation/resources/HelpErrors.resx +++ b/src/System.Management.Automation/resources/HelpErrors.resx @@ -179,7 +179,7 @@ To update these Help topics, start PowerShell by using the "Run as Administrator" command, and try running Update-Help again.</value> </data> <data name="GraphicalHostAssemblyIsNotFound" xml:space="preserve"> - <value>To use the {0}, install Windows PowerShell ISE by using Server Manager, and then restart this application. ({1})</value> + <value>To use the {0}, make sure your application uses 'Microsoft.NET.Sdk.WindowsDesktop' as the project SDK and the corresponding assembly 'Microsoft.PowerShell.GraphicalHost' is available. ({1})</value> </data> <data name="RemotingNotSupportedForFeature" xml:space="preserve"> <value>{0} does not work in a remote session.</value> diff --git a/src/System.Management.Automation/resources/Metadata.resx b/src/System.Management.Automation/resources/Metadata.resx index de0a30f77e2..b8dc3ea4eb4 100644 --- a/src/System.Management.Automation/resources/Metadata.resx +++ b/src/System.Management.Automation/resources/Metadata.resx @@ -175,7 +175,7 @@ <value>The character length ({1}) of the argument is too short. Specify an argument with a length that is greater than or equal to "{0}", and then try the command again.</value> </data> <data name="ValidateLengthMaxLengthFailure" xml:space="preserve"> - <value>The character length of the {1} argument is too long. Shorten the character length of the argument so it is fewer than or equal to "{0}" characters, and then try the command again.</value> + <value>The character length ({1}) of the argument is too long. Specify an argument with a length that is shorter than or equal to "{0}", and then try the command again.</value> </data> <data name="ValidateSetFailure" xml:space="preserve"> <value>The argument "{0}" does not belong to the set "{1}" specified by the ValidateSet attribute. Supply an argument that is in the set and then try the command again.</value> diff --git a/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx b/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx index 9e572797bd2..b5905f1cf7a 100644 --- a/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx +++ b/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx @@ -849,7 +849,7 @@ Note that 'Start-Job' is not supported by design in scenarios where PowerShell i <value>The WriteEvents parameter cannot be used without the Wait parameter.</value> </data> <data name="PowerShellVersionNotSupported" xml:space="preserve"> - <value>PowerShell remoting endpoint versioning is not supported on PowerShell Core.</value> + <value>PowerShell remoting endpoint versioning is not supported on PowerShell 7+.</value> </data> <data name="JobManagerRegistrationConstructorError" xml:space="preserve"> <value>The following type cannot be instantiated because its constructor is not public: {0}.</value> @@ -1411,7 +1411,7 @@ All WinRM sessions connected to PowerShell session configurations, such as Micro <value>Multiple processes were found with this name {0}. Use the process Id to specify a single process to enter.</value> </data> <data name="EnterPSHostProcessNoPowerShell" xml:space="preserve"> - <value>Cannot enter process with Id '{0}' because it has not loaded the PowerShell engine.</value> + <value>Cannot enter process with Id '{0}' because it has not loaded the PowerShell engine or the named-pipe listener was disabled.</value> </data> <data name="EnterPSHostProcessNoProcessFoundWithId" xml:space="preserve"> <value>No process was found with Id: {0}.</value> @@ -1726,4 +1726,10 @@ SSH client process terminated before connection could be established.</value> <data name="HyperVFailedToGetStateUnknownType" xml:space="preserve"> <value>Failed to get Hyper-V VM State. The value was of the type {0} but was expected to be Microsoft.HyperV.PowerShell.VMState or System.String.</value> </data> + <data name="HyperVInvalidResponse" xml:space="preserve"> + <value>Hyper-V {0} sent an invalid {1} response during the connection negotiation.</value> + </data> + <data name="HyperVNegotiationFailed" xml:space="preserve"> + <value>Negotiating a secure connection to Hyper-V failed. Make sure the Host and Guest are updated with all relevant Microsoft Updates.</value> + </data> </root> diff --git a/src/System.Management.Automation/resources/RunspaceInit.resx b/src/System.Management.Automation/resources/RunspaceInit.resx index f12ba3f71c5..985056b69ee 100644 --- a/src/System.Management.Automation/resources/RunspaceInit.resx +++ b/src/System.Management.Automation/resources/RunspaceInit.resx @@ -153,6 +153,9 @@ <data name="OutputEncodingDescription" xml:space="preserve"> <value>The text encoding used when piping text to a native executable file</value> </data> + <data name="PSApplicationOutputEncodingDescription" xml:space="preserve"> + <value>The text encoding used when reading output text from a native executable file</value> + </data> <data name="PSStyleDescription" xml:space="preserve"> <value>Configuration controlling how text is rendered.</value> </data> diff --git a/src/System.Management.Automation/resources/SuggestionStrings.resx b/src/System.Management.Automation/resources/SuggestionStrings.resx index 9e325b2616c..39fbc3469f2 100644 --- a/src/System.Management.Automation/resources/SuggestionStrings.resx +++ b/src/System.Management.Automation/resources/SuggestionStrings.resx @@ -123,16 +123,7 @@ PowerShell does not load commands from the current location by default (see 'Get If you trust this command, run the following command instead:</value> </data> - <data name="Suggestion_CommandExistsInCurrentDirectory_Legacy" xml:space="preserve"> - <value>The command "{0}" was not found, but does exist in the current location. PowerShell does not load commands from the current location by default. If you trust this command, instead type: "{1}". See "get-help about_Command_Precedence" for more details.</value> - </data> <data name="Suggestion_CommandNotFound" xml:space="preserve"> <value>The most similar commands are:</value> </data> - <data name="RuleMustBeScriptBlock" xml:space="preserve"> - <value>Rule must be a ScriptBlock for dynamic match types.</value> - </data> - <data name="InvalidMatchType" xml:space="preserve"> - <value>MatchType must be 'Command', 'Error', or 'Dynamic'.</value> - </data> </root> diff --git a/src/System.Management.Automation/resources/TabCompletionStrings.resx b/src/System.Management.Automation/resources/TabCompletionStrings.resx index 751e04cfd2b..298de92da3c 100644 --- a/src/System.Management.Automation/resources/TabCompletionStrings.resx +++ b/src/System.Management.Automation/resources/TabCompletionStrings.resx @@ -457,10 +457,10 @@ This must be the last parameter on the #requires statement line.</value> Specifies the minimum version of PowerShell that the script requires.</value> </data> <data name="RequiresPsEditionCoreDescription" xml:space="preserve"> - <value>Specifies that the script requires PowerShell Core to run.</value> + <value>Specifies that the script requires PowerShell 7+ to run.</value> </data> <data name="RequiresPsEditionDesktopDescription" xml:space="preserve"> - <value>Specifies that the script requires Windows PowerShell to run.</value> + <value>Specifies that the script requires Windows PowerShell 5.1 to run.</value> </data> <data name="RequiresModuleSpecModuleNameDescription" xml:space="preserve"> <value>[string] diff --git a/src/System.Management.Automation/security/Authenticode.cs b/src/System.Management.Automation/security/Authenticode.cs index 591f64d6298..3a7720616d1 100644 --- a/src/System.Management.Automation/security/Authenticode.cs +++ b/src/System.Management.Automation/security/Authenticode.cs @@ -115,8 +115,8 @@ internal static Signature SignFile(SigningOption option, if (!string.IsNullOrEmpty(timeStampServerUrl)) { if ((timeStampServerUrl.Length <= 7) || ( - (timeStampServerUrl.IndexOf("http://", StringComparison.OrdinalIgnoreCase) != 0) && - (timeStampServerUrl.IndexOf("https://", StringComparison.OrdinalIgnoreCase) != 0))) + !timeStampServerUrl.StartsWith("http://", StringComparison.OrdinalIgnoreCase) && + !timeStampServerUrl.StartsWith("https://", StringComparison.OrdinalIgnoreCase))) { throw PSTraceSource.NewArgumentException( nameof(certificate), diff --git a/src/System.Management.Automation/security/CredentialParameter.cs b/src/System.Management.Automation/security/CredentialParameter.cs index 0d48e4fa738..1ea19691ae7 100644 --- a/src/System.Management.Automation/security/CredentialParameter.cs +++ b/src/System.Management.Automation/security/CredentialParameter.cs @@ -89,4 +89,3 @@ public override object Transform(EngineIntrinsics engineIntrinsics, object input } #pragma warning restore 56506 - diff --git a/src/System.Management.Automation/security/MshSignature.cs b/src/System.Management.Automation/security/MshSignature.cs index fd8dd4f67ef..7cbaf98d3a5 100644 --- a/src/System.Management.Automation/security/MshSignature.cs +++ b/src/System.Management.Automation/security/MshSignature.cs @@ -185,6 +185,11 @@ public string Path /// </summary> public bool IsOSBinary { get; internal set; } + /// <summary> + /// Gets the Subject Alternative Name from the signer certificate. + /// </summary> + public string[] SubjectAlternativeName { get; private set; } + /// <summary> /// Constructor for class Signature /// @@ -277,6 +282,9 @@ private void Init(string filePath, _statusMessage = GetSignatureStatusMessage(isc, error, filePath); + + // Extract Subject Alternative Name from the signer certificate + SubjectAlternativeName = GetSubjectAlternativeName(signer); } private static SignatureStatus GetSignatureStatusFromWin32Error(DWORD error) @@ -389,5 +397,34 @@ private static string GetSignatureStatusMessage(SignatureStatus status, return message; } + + /// <summary> + /// Extracts the Subject Alternative Name from the certificate. + /// </summary> + /// <param name="certificate">The certificate to extract SAN from.</param> + /// <returns>Array of SAN entries or null if not found.</returns> + private static string[] GetSubjectAlternativeName(X509Certificate2 certificate) + { + if (certificate == null) + { + return null; + } + + foreach (X509Extension extension in certificate.Extensions) + { + if (extension.Oid != null && extension.Oid.Value == CertificateFilterInfo.SubjectAlternativeNameOid) + { + string formatted = extension.Format(multiLine: true); + if (string.IsNullOrEmpty(formatted)) + { + return null; + } + + return formatted.Split(new[] { "\r\n", "\n", "\r" }, StringSplitOptions.RemoveEmptyEntries); + } + } + + return null; + } } } diff --git a/src/System.Management.Automation/security/SecureStringHelper.cs b/src/System.Management.Automation/security/SecureStringHelper.cs index d06e33f9567..5ff5881bab4 100644 --- a/src/System.Management.Automation/security/SecureStringHelper.cs +++ b/src/System.Management.Automation/security/SecureStringHelper.cs @@ -594,7 +594,7 @@ internal static class CAPI internal const int E_FILENOTFOUND = unchecked((int)0x80070002); // File not found internal const int ERROR_FILE_NOT_FOUND = 2; // File not found - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct CRYPTOAPI_BLOB { internal uint cbData; diff --git a/src/System.Management.Automation/security/SecuritySupport.cs b/src/System.Management.Automation/security/SecuritySupport.cs index e6a6f10416b..dc6d048c5b1 100644 --- a/src/System.Management.Automation/security/SecuritySupport.cs +++ b/src/System.Management.Automation/security/SecuritySupport.cs @@ -815,6 +815,7 @@ internal DateTime Expiring // The OID arc 1.3.6.1.4.1.311.80 is assigned to PowerShell. If we need // new OIDs, we can assign them under this branch. internal const string DocumentEncryptionOid = "1.3.6.1.4.1.311.80.1"; + internal const string SubjectAlternativeNameOid = "2.5.29.17"; } } @@ -1606,10 +1607,8 @@ internal static void CurrentDomain_ProcessExit(object sender, EventArgs e) } } - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private static IntPtr s_amsiContext = IntPtr.Zero; - [SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")] private static IntPtr s_amsiSession = IntPtr.Zero; private static readonly bool s_amsiInitFailed = false; diff --git a/src/System.Management.Automation/security/nativeMethods.cs b/src/System.Management.Automation/security/nativeMethods.cs index ae880e05518..6dd8f59d613 100644 --- a/src/System.Management.Automation/security/nativeMethods.cs +++ b/src/System.Management.Automation/security/nativeMethods.cs @@ -1287,28 +1287,28 @@ internal enum SecurityInformation : uint UNPROTECTED_SACL_SECURITY_INFORMATION = 0x10000000 } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct LUID { internal uint LowPart; internal uint HighPart; } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct LUID_AND_ATTRIBUTES { internal LUID Luid; internal uint Attributes; } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct TOKEN_PRIVILEGE { internal uint PrivilegeCount; internal LUID_AND_ATTRIBUTES Privilege; } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct ACL { internal byte AclRevision; @@ -1318,7 +1318,7 @@ internal struct ACL internal ushort Sbz2; } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct ACE_HEADER { internal byte AceType; @@ -1326,7 +1326,7 @@ internal struct ACE_HEADER internal ushort AceSize; } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct SYSTEM_AUDIT_ACE { internal ACE_HEADER Header; @@ -1334,7 +1334,7 @@ internal struct SYSTEM_AUDIT_ACE internal uint SidStart; } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct LSA_UNICODE_STRING { internal ushort Length; @@ -1342,7 +1342,7 @@ internal struct LSA_UNICODE_STRING internal IntPtr Buffer; } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct CENTRAL_ACCESS_POLICY { internal IntPtr CAPID; diff --git a/src/System.Management.Automation/singleshell/config/MshSnapinLoadException.cs b/src/System.Management.Automation/singleshell/config/MshSnapinLoadException.cs index f0e61f01a9e..71c81611440 100644 --- a/src/System.Management.Automation/singleshell/config/MshSnapinLoadException.cs +++ b/src/System.Management.Automation/singleshell/config/MshSnapinLoadException.cs @@ -171,7 +171,7 @@ public override string Message /// </summary> /// <param name="info">Serialization information.</param> /// <param name="context">Streaming context.</param> - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PSSnapInException(SerializationInfo info, StreamingContext context) { diff --git a/src/System.Management.Automation/utils/CommandDiscoveryExceptions.cs b/src/System.Management.Automation/utils/CommandDiscoveryExceptions.cs index 6c2380023ca..89580932359 100644 --- a/src/System.Management.Automation/utils/CommandDiscoveryExceptions.cs +++ b/src/System.Management.Automation/utils/CommandDiscoveryExceptions.cs @@ -78,7 +78,7 @@ public CommandNotFoundException(string message, Exception innerException) : base /// <param name="context"> /// streaming context /// </param> - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected CommandNotFoundException(SerializationInfo info, StreamingContext context) { @@ -336,13 +336,13 @@ public ScriptRequiresException(string message, Exception innerException) : base( /// <param name="context"> /// streaming context /// </param> - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ScriptRequiresException(SerializationInfo info, StreamingContext context) { throw new NotSupportedException(); } - + #endregion Serialization #region Properties diff --git a/src/System.Management.Automation/utils/CommandProcessorExceptions.cs b/src/System.Management.Automation/utils/CommandProcessorExceptions.cs index f63ff69bd2c..668c95a25e6 100644 --- a/src/System.Management.Automation/utils/CommandProcessorExceptions.cs +++ b/src/System.Management.Automation/utils/CommandProcessorExceptions.cs @@ -24,7 +24,7 @@ public class ApplicationFailedException : RuntimeException /// <param name="info">The serialization information to use when initializing this object.</param> /// <param name="context">The streaming context to use when initializing this object.</param> /// <returns>Constructed object.</returns> - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ApplicationFailedException(SerializationInfo info, StreamingContext context) { diff --git a/src/System.Management.Automation/utils/CryptoUtils.cs b/src/System.Management.Automation/utils/CryptoUtils.cs index dfda3032728..c82c96ecdc3 100644 --- a/src/System.Management.Automation/utils/CryptoUtils.cs +++ b/src/System.Management.Automation/utils/CryptoUtils.cs @@ -576,21 +576,12 @@ internal static PSRSACryptoServiceProvider GetRSACryptoServiceProviderForServer( #region IDisposable /// <summary> - /// Dispose resources. + /// Release all resources. /// </summary> public void Dispose() { - Dispose(true); - System.GC.SuppressFinalize(this); - } - - private void Dispose(bool disposing) - { - if (disposing) - { - _rsa?.Dispose(); - _aes?.Dispose(); - } + _rsa?.Dispose(); + _aes?.Dispose(); } #endregion IDisposable diff --git a/src/System.Management.Automation/utils/ExecutionExceptions.cs b/src/System.Management.Automation/utils/ExecutionExceptions.cs index 69a58d326b2..ff41e045bbb 100644 --- a/src/System.Management.Automation/utils/ExecutionExceptions.cs +++ b/src/System.Management.Automation/utils/ExecutionExceptions.cs @@ -114,12 +114,12 @@ public CmdletInvocationException(string message, /// <param name="info">Serialization information.</param> /// <param name="context">Streaming context.</param> /// <returns>Constructed object.</returns> - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected CmdletInvocationException(SerializationInfo info, StreamingContext context) { throw new NotSupportedException(); - } + } #endregion Serialization #endregion ctor @@ -154,7 +154,7 @@ public override ErrorRecord ErrorRecord /// <see cref="System.Management.Automation.ProviderInvocationException"/>. /// This is generally reported from the standard provider navigation cmdlets /// such as get-childitem. - /// </summary> + /// </summary> public class CmdletProviderInvocationException : CmdletInvocationException { #region ctor @@ -193,7 +193,7 @@ public CmdletProviderInvocationException() /// <param name="info">Serialization information.</param> /// <param name="context">Streaming context.</param> /// <returns>Constructed object.</returns> - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected CmdletProviderInvocationException(SerializationInfo info, StreamingContext context) { @@ -281,7 +281,7 @@ private static Exception GetInnerException(Exception e) /// Catching this exception is optional; if the cmdlet or providers chooses not to /// handle PipelineStoppedException and instead allow it to propagate to the /// PowerShell Engine's call to ProcessRecord, the PowerShell Engine will handle it properly. - /// </remarks> + /// </remarks> public class PipelineStoppedException : RuntimeException { #region ctor @@ -304,7 +304,7 @@ public PipelineStoppedException() /// <param name="info">Serialization information.</param> /// <param name="context">Streaming context.</param> /// <returns>Constructed object.</returns> - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PipelineStoppedException(SerializationInfo info, StreamingContext context) { @@ -342,7 +342,7 @@ public PipelineStoppedException(string message, /// to an asynchronous pipeline source and the pipeline has already /// been stopped. /// </summary> - /// <seealso cref="System.Management.Automation.Runspaces.Pipeline.Input"/> + /// <seealso cref="System.Management.Automation.Runspaces.Pipeline.Input"/> public class PipelineClosedException : RuntimeException { #region ctor @@ -387,7 +387,7 @@ public PipelineClosedException(string message, /// <param name="info">Serialization information.</param> /// <param name="context">Streaming context.</param> /// <returns>Constructed object.</returns> - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PipelineClosedException(SerializationInfo info, StreamingContext context) { @@ -405,7 +405,7 @@ protected PipelineClosedException(SerializationInfo info, /// <remarks> /// For example, if $WarningPreference is "Stop", the command will fail with /// this error if a cmdlet calls WriteWarning. - /// </remarks> + /// </remarks> public class ActionPreferenceStopException : RuntimeException { #region ctor @@ -467,12 +467,12 @@ internal ActionPreferenceStopException(InvocationInfo invocationInfo, /// <param name="info">Serialization information.</param> /// <param name="context">Streaming context.</param> /// <returns>Constructed object.</returns> - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ActionPreferenceStopException(SerializationInfo info, StreamingContext context) { throw new NotSupportedException(); - } + } #endregion Serialization /// <summary> @@ -612,7 +612,7 @@ public ParentContainsErrorRecordException(string message, /// <param name="context">Streaming context.</param> /// <returns>Doesn't return.</returns> /// <exception cref="NotImplementedException">Always.</exception> - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ParentContainsErrorRecordException( SerializationInfo info, StreamingContext context) { @@ -648,7 +648,7 @@ public override string Message /// The redirected object is available as /// <see cref="System.Management.Automation.ErrorRecord.TargetObject"/> /// in the ErrorRecord which contains this exception. - /// </remarks> + /// </remarks> public class RedirectedException : RuntimeException { #region constructors @@ -697,7 +697,7 @@ public RedirectedException(string message, /// <param name="info">Serialization information.</param> /// <param name="context">Streaming context.</param> /// <returns>Constructed object.</returns> - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected RedirectedException(SerializationInfo info, StreamingContext context) { @@ -719,7 +719,7 @@ protected RedirectedException(SerializationInfo info, /// call depth to prevent stack overflows. The maximum call depth is configurable /// but generally high enough that scripts which are not deeply recursive /// should not have a problem. - /// </remarks> + /// </remarks> public class ScriptCallDepthException : SystemException, IContainsErrorRecord { #region ctor @@ -765,7 +765,7 @@ public ScriptCallDepthException(string message, /// <param name="info">Serialization information.</param> /// <param name="context">Streaming context.</param> /// <returns>Constructed object.</returns> - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ScriptCallDepthException(SerializationInfo info, StreamingContext context) { @@ -860,11 +860,11 @@ public PipelineDepthException(string message, /// <param name="info">Serialization information.</param> /// <param name="context">Streaming context.</param> /// <returns>Constructed object.</returns> - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PipelineDepthException(SerializationInfo info, - StreamingContext context) + StreamingContext context) { - throw new NotSupportedException(); + throw new NotSupportedException(); } #endregion Serialization @@ -918,7 +918,7 @@ public int CallDepth /// /// Note that HaltCommandException does not define IContainsErrorRecord. /// This is because it is not reported to the user. - /// </remarks> + /// </remarks> public class HaltCommandException : SystemException { #region ctor @@ -963,7 +963,7 @@ public HaltCommandException(string message, /// <param name="info">Serialization information.</param> /// <param name="context">Streaming context.</param> /// <returns>Constructed object.</returns> - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected HaltCommandException(SerializationInfo info, StreamingContext context) { diff --git a/src/System.Management.Automation/utils/GraphicalHostReflectionWrapper.cs b/src/System.Management.Automation/utils/GraphicalHostReflectionWrapper.cs index a0b12f986de..ec780222345 100644 --- a/src/System.Management.Automation/utils/GraphicalHostReflectionWrapper.cs +++ b/src/System.Management.Automation/utils/GraphicalHostReflectionWrapper.cs @@ -55,7 +55,7 @@ private GraphicalHostReflectionWrapper() /// <exception cref="RuntimeException">When it was not possible to load Microsoft.PowerShell.GraphicalHost.dlly.</exception> internal static GraphicalHostReflectionWrapper GetGraphicalHostReflectionWrapper(PSCmdlet parentCmdlet, string graphicalHostHelperTypeName) { - return GraphicalHostReflectionWrapper.GetGraphicalHostReflectionWrapper(parentCmdlet, graphicalHostHelperTypeName, parentCmdlet.CommandInfo.Name); + return GetGraphicalHostReflectionWrapper(parentCmdlet, graphicalHostHelperTypeName, parentCmdlet.CommandInfo.Name); } /// <summary> @@ -73,9 +73,9 @@ internal static GraphicalHostReflectionWrapper GetGraphicalHostReflectionWrapper [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Assembly.Load has been found to throw unadvertised exceptions")] internal static GraphicalHostReflectionWrapper GetGraphicalHostReflectionWrapper(PSCmdlet parentCmdlet, string graphicalHostHelperTypeName, string featureName) { - GraphicalHostReflectionWrapper returnValue = new GraphicalHostReflectionWrapper(); + GraphicalHostReflectionWrapper returnValue = new(); - if (GraphicalHostReflectionWrapper.IsInputFromRemoting(parentCmdlet)) + if (IsInputFromRemoting(parentCmdlet)) { ErrorRecord error = new ErrorRecord( new NotSupportedException(StringUtil.Format(HelpErrors.RemotingNotSupportedForFeature, featureName)), @@ -87,9 +87,10 @@ internal static GraphicalHostReflectionWrapper GetGraphicalHostReflectionWrapper } // Prepare the full assembly name. - AssemblyName graphicalHostAssemblyName = new AssemblyName(); + AssemblyName smaAssemblyName = typeof(PSObject).Assembly.GetName(); + AssemblyName graphicalHostAssemblyName = new(); graphicalHostAssemblyName.Name = "Microsoft.PowerShell.GraphicalHost"; - graphicalHostAssemblyName.Version = new Version(3, 0, 0, 0); + graphicalHostAssemblyName.Version = smaAssemblyName.Version; graphicalHostAssemblyName.CultureInfo = new CultureInfo(string.Empty); // Neutral culture graphicalHostAssemblyName.SetPublicKeyToken(new byte[] { 0x31, 0xbf, 0x38, 0x56, 0xad, 0x36, 0x4e, 0x35 }); @@ -124,7 +125,7 @@ internal static GraphicalHostReflectionWrapper GetGraphicalHostReflectionWrapper returnValue._graphicalHostHelperType = returnValue._graphicalHostAssembly.GetType(graphicalHostHelperTypeName); - Diagnostics.Assert(returnValue._graphicalHostHelperType != null, "the type exists in Microsoft.PowerShell.GraphicalHost"); + Diagnostics.Assert(returnValue._graphicalHostHelperType != null, "the type should exist in Microsoft.PowerShell.GraphicalHost"); ConstructorInfo constructor = returnValue._graphicalHostHelperType.GetConstructor( BindingFlags.NonPublic | BindingFlags.Instance, null, diff --git a/src/System.Management.Automation/utils/HostInterfacesExceptions.cs b/src/System.Management.Automation/utils/HostInterfacesExceptions.cs index 362a1448207..df36d248d18 100644 --- a/src/System.Management.Automation/utils/HostInterfacesExceptions.cs +++ b/src/System.Management.Automation/utils/HostInterfacesExceptions.cs @@ -100,7 +100,7 @@ class HostException : RuntimeException /// <param name="context"> /// The contextual information about the source or destination. /// </param> - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected HostException(SerializationInfo info, StreamingContext context) { @@ -120,7 +120,7 @@ private void SetDefaultErrorRecord() /// <summary> /// Defines the exception thrown when an error occurs from prompting for a command parameter. - /// </summary> + /// </summary> public class PromptingException : HostException { @@ -209,7 +209,7 @@ class PromptingException : HostException /// <param name="context"> /// The contextual information about the source or destination. /// </param> - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PromptingException(SerializationInfo info, StreamingContext context) { diff --git a/src/System.Management.Automation/utils/MetadataExceptions.cs b/src/System.Management.Automation/utils/MetadataExceptions.cs index f2c4ace8647..24660d348e7 100644 --- a/src/System.Management.Automation/utils/MetadataExceptions.cs +++ b/src/System.Management.Automation/utils/MetadataExceptions.cs @@ -71,7 +71,6 @@ internal MetadataException( /// <summary> /// Defines the exception thrown for all Validate attributes. /// </summary> - [SuppressMessage("Microsoft.Usage", "CA2240:ImplementISerializableCorrectly")] public class ValidationMetadataException : MetadataException { internal const string ValidateRangeElementType = "ValidateRangeElementType"; diff --git a/src/System.Management.Automation/utils/MshArgumentException.cs b/src/System.Management.Automation/utils/MshArgumentException.cs index baa3f8c3163..452527d1c18 100644 --- a/src/System.Management.Automation/utils/MshArgumentException.cs +++ b/src/System.Management.Automation/utils/MshArgumentException.cs @@ -68,7 +68,7 @@ public PSArgumentException(string message, string paramName) /// <param name="info">Serialization information.</param> /// <param name="context">Streaming context.</param> /// <returns>Constructed object.</returns> - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PSArgumentException(SerializationInfo info, StreamingContext context) { diff --git a/src/System.Management.Automation/utils/MshArgumentNullException.cs b/src/System.Management.Automation/utils/MshArgumentNullException.cs index a4f50256e44..16ef442e5ec 100644 --- a/src/System.Management.Automation/utils/MshArgumentNullException.cs +++ b/src/System.Management.Automation/utils/MshArgumentNullException.cs @@ -79,12 +79,12 @@ public PSArgumentNullException(string paramName, string message) /// <param name="info">Serialization information.</param> /// <param name="context">Streaming context.</param> /// <returns>Constructed object.</returns> - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PSArgumentNullException(SerializationInfo info, StreamingContext context) { throw new NotSupportedException(); - } + } #endregion Serialization #endregion ctor diff --git a/src/System.Management.Automation/utils/MshArgumentOutOfRangeException.cs b/src/System.Management.Automation/utils/MshArgumentOutOfRangeException.cs index 8d666778a4f..4e65ed0acb1 100644 --- a/src/System.Management.Automation/utils/MshArgumentOutOfRangeException.cs +++ b/src/System.Management.Automation/utils/MshArgumentOutOfRangeException.cs @@ -67,13 +67,13 @@ public PSArgumentOutOfRangeException(string paramName, object actualValue, strin /// <param name="info">Serialization information.</param> /// <param name="context">Streaming context.</param> /// <returns>Constructed object.</returns> - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PSArgumentOutOfRangeException(SerializationInfo info, StreamingContext context) { throw new NotSupportedException(); } - + #endregion Serialization /// <summary> diff --git a/src/System.Management.Automation/utils/MshInvalidOperationException.cs b/src/System.Management.Automation/utils/MshInvalidOperationException.cs index f398e4a2e99..8400e762360 100644 --- a/src/System.Management.Automation/utils/MshInvalidOperationException.cs +++ b/src/System.Management.Automation/utils/MshInvalidOperationException.cs @@ -38,7 +38,7 @@ public PSInvalidOperationException() /// <param name="info">Serialization information.</param> /// <param name="context">Streaming context.</param> /// <returns>Constructed object.</returns> - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PSInvalidOperationException(SerializationInfo info, StreamingContext context) { diff --git a/src/System.Management.Automation/utils/MshNotImplementedException.cs b/src/System.Management.Automation/utils/MshNotImplementedException.cs index 7d1082bf747..1b925053410 100644 --- a/src/System.Management.Automation/utils/MshNotImplementedException.cs +++ b/src/System.Management.Automation/utils/MshNotImplementedException.cs @@ -38,12 +38,12 @@ public PSNotImplementedException() /// <param name="info">Serialization information.</param> /// <param name="context">Streaming context.</param> /// <returns>Constructed object.</returns> - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PSNotImplementedException(SerializationInfo info, StreamingContext context) { throw new NotSupportedException(); - } + } #endregion Serialization /// <summary> diff --git a/src/System.Management.Automation/utils/MshNotSupportedException.cs b/src/System.Management.Automation/utils/MshNotSupportedException.cs index 1268636742b..a1e519a960e 100644 --- a/src/System.Management.Automation/utils/MshNotSupportedException.cs +++ b/src/System.Management.Automation/utils/MshNotSupportedException.cs @@ -38,13 +38,13 @@ public PSNotSupportedException() /// <param name="info">Serialization information.</param> /// <param name="context">Streaming context.</param> /// <returns>Constructed object.</returns> - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected PSNotSupportedException(SerializationInfo info, StreamingContext context) { throw new NotSupportedException(); } - + #endregion Serialization /// <summary> diff --git a/src/System.Management.Automation/utils/ParameterBinderExceptions.cs b/src/System.Management.Automation/utils/ParameterBinderExceptions.cs index 4052eec0b81..a28de586d8e 100644 --- a/src/System.Management.Automation/utils/ParameterBinderExceptions.cs +++ b/src/System.Management.Automation/utils/ParameterBinderExceptions.cs @@ -297,7 +297,7 @@ internal ParameterBindingException( /// <param name="context"> /// streaming context /// </param> - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ParameterBindingException( SerializationInfo info, StreamingContext context) @@ -486,7 +486,7 @@ private string BuildMessage() #endregion Private } - + internal class ParameterBindingValidationException : ParameterBindingException { #region Preferred constructors @@ -654,7 +654,7 @@ internal ParameterBindingValidationException( /// <param name="context"> /// streaming context /// </param> - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ParameterBindingValidationException( SerializationInfo info, StreamingContext context) @@ -844,7 +844,7 @@ internal ParameterBindingArgumentTransformationException( /// <param name="context"> /// streaming context /// </param> - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ParameterBindingArgumentTransformationException( SerializationInfo info, StreamingContext context) @@ -854,7 +854,7 @@ protected ParameterBindingArgumentTransformationException( #endregion serialization } - + internal class ParameterBindingParameterDefaultValueException : ParameterBindingException { #region Preferred constructors @@ -1018,7 +1018,7 @@ internal ParameterBindingParameterDefaultValueException( /// <param name="context"> /// streaming context /// </param> - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected ParameterBindingParameterDefaultValueException( SerializationInfo info, StreamingContext context) diff --git a/src/System.Management.Automation/utils/PlatformInvokes.cs b/src/System.Management.Automation/utils/PlatformInvokes.cs index 0993a9c1348..f9e78f5d1d3 100644 --- a/src/System.Management.Automation/utils/PlatformInvokes.cs +++ b/src/System.Management.Automation/utils/PlatformInvokes.cs @@ -235,7 +235,6 @@ internal static extern IntPtr CreateFile( /// </returns> [DllImport(PinvokeDllNames.CloseHandleDllName, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] - // [SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage")] internal static extern bool CloseHandle(IntPtr handle); [DllImport(PinvokeDllNames.DosDateTimeToFileTimeDllName, SetLastError = false)] @@ -412,7 +411,6 @@ internal static bool RestoreTokenPrivilege(string privilegeName, ref TOKEN_PRIVI /// <param name="lpLuid"></param> /// <returns></returns> [DllImport(PinvokeDllNames.LookupPrivilegeValueDllName, CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false)] - [SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage")] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, ref LUID lpLuid); @@ -424,7 +422,6 @@ internal static bool RestoreTokenPrivilege(string privilegeName, ref TOKEN_PRIVI /// <param name="pfResult"></param> /// <returns></returns> [DllImport(PinvokeDllNames.PrivilegeCheckDllName, CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false)] - [SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage")] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool PrivilegeCheck(IntPtr tokenHandler, ref PRIVILEGE_SET requiredPrivileges, out bool pfResult); @@ -441,35 +438,34 @@ internal static bool RestoreTokenPrivilege(string privilegeName, ref TOKEN_PRIVI /// <param name="returnLength"></param> /// <returns></returns> [DllImport(PinvokeDllNames.AdjustTokenPrivilegesDllName, CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false)] - [SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage")] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool AdjustTokenPrivileges(IntPtr tokenHandler, bool disableAllPrivilege, ref TOKEN_PRIVILEGE newPrivilegeState, int bufferLength, out TOKEN_PRIVILEGE previousPrivilegeState, ref int returnLength); - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct TOKEN_PRIVILEGE { internal uint PrivilegeCount; internal LUID_AND_ATTRIBUTES Privilege; } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct LUID { internal uint LowPart; internal uint HighPart; } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct LUID_AND_ATTRIBUTES { internal LUID Luid; internal uint Attributes; } - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + [StructLayout(LayoutKind.Sequential)] internal struct PRIVILEGE_SET { internal uint PrivilegeCount; @@ -482,7 +478,6 @@ internal struct PRIVILEGE_SET /// </summary> /// <returns></returns> [DllImport(PinvokeDllNames.GetCurrentProcessDllName)] - [SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage")] internal static extern IntPtr GetCurrentProcess(); /// <summary> @@ -494,7 +489,6 @@ internal struct PRIVILEGE_SET /// <param name="tokenHandle">Process token.</param> /// <returns>The current process token.</returns> [DllImport(PinvokeDllNames.OpenProcessTokenDllName, CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false)] - [SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage")] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool OpenProcessToken(IntPtr processHandle, uint desiredAccess, out IntPtr tokenHandle); diff --git a/src/System.Management.Automation/utils/PsUtils.cs b/src/System.Management.Automation/utils/PsUtils.cs index d404581bf1f..0a4682bcdd6 100644 --- a/src/System.Management.Automation/utils/PsUtils.cs +++ b/src/System.Management.Automation/utils/PsUtils.cs @@ -308,7 +308,7 @@ internal static Hashtable EvaluatePowerShellDataFile( ex.Message); } - if (!(evaluationResult is Hashtable retResult)) + if (evaluationResult is not Hashtable retResult) { throw PSTraceSource.NewInvalidOperationException( ParserStrings.InvalidPowerShellDataFile, @@ -340,7 +340,7 @@ internal static Hashtable EvaluatePowerShellDataFile( internal static Hashtable GetModuleManifestProperties(string psDataFilePath, string[] keys) { - string dataFileContents = ScriptAnalysis.ReadScript(psDataFilePath); + string dataFileContents = File.ReadAllText(psDataFilePath, Encoding.Default); ParseError[] parseErrors; var ast = (new Parser()).Parse(psDataFilePath, dataFileContents, null, out parseErrors, ParseMode.ModuleAnalysis); if (parseErrors.Length > 0) @@ -453,14 +453,14 @@ internal static object[] Base64ToArgsConverter(string base64) throw PSTraceSource.NewArgumentException(MinishellParameterBinderController.ArgsParameter); } - if (!(dso is PSObject mo)) + if (dso is not PSObject mo) { // This helper function should move the host. Provide appropriate error message. // Format of args parameter is not correct. throw PSTraceSource.NewArgumentException(MinishellParameterBinderController.ArgsParameter); } - if (!(mo.BaseObject is ArrayList argsList)) + if (mo.BaseObject is not ArrayList argsList) { // This helper function should move the host. Provide appropriate error message. // Format of args parameter is not correct. diff --git a/src/System.Management.Automation/utils/RuntimeException.cs b/src/System.Management.Automation/utils/RuntimeException.cs index 4cfdc31bcb6..ab013359d5c 100644 --- a/src/System.Management.Automation/utils/RuntimeException.cs +++ b/src/System.Management.Automation/utils/RuntimeException.cs @@ -40,12 +40,12 @@ public RuntimeException() /// <param name="info">Serialization information.</param> /// <param name="context">Streaming context.</param> /// <returns>Constructed object.</returns> - [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] + [Obsolete("Legacy serialization support is deprecated since .NET 8", DiagnosticId = "SYSLIB0051")] protected RuntimeException(SerializationInfo info, StreamingContext context) { throw new NotSupportedException(); - } + } #endregion Serialization /// <summary> @@ -221,7 +221,7 @@ internal static string RetrieveMessage(Exception e) if (e == null) return string.Empty; - if (!(e is IContainsErrorRecord icer)) + if (e is not IContainsErrorRecord icer) return e.Message; ErrorRecord er = icer.ErrorRecord; if (er == null) diff --git a/src/System.Management.Automation/utils/Telemetry.cs b/src/System.Management.Automation/utils/Telemetry.cs index 13a8d7d5ec5..fbb7f588283 100644 --- a/src/System.Management.Automation/utils/Telemetry.cs +++ b/src/System.Management.Automation/utils/Telemetry.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Management.Automation; using System.Runtime.InteropServices; -using System.Security.AccessControl; using System.Threading; using Microsoft.ApplicationInsights; @@ -164,8 +163,10 @@ public static class ApplicationInsightsTelemetry private static readonly HashSet<string> s_knownSubsystemNames; + private static readonly string s_uuidPath; + /// <summary>Gets a value indicating whether telemetry can be sent.</summary> - public static bool CanSendTelemetry { get; private set; } = false; + public static bool CanSendTelemetry { get; private set; } /// <summary> /// Initializes static members of the <see cref="ApplicationInsightsTelemetry"/> class. @@ -176,537 +177,483 @@ public static class ApplicationInsightsTelemetry /// </summary> static ApplicationInsightsTelemetry() { - // If we can't send telemetry, there's no reason to do any of this - CanSendTelemetry = !GetEnvironmentVariableAsBool(name: _telemetryOptoutEnvVar, defaultValue: false); + CanSendTelemetry = !Utils.GetEnvironmentVariableAsBool(name: _telemetryOptoutEnvVar, defaultValue: false) + && Platform.TryDeriveFromCache("telemetry.uuid", out s_uuidPath); + +#if !UNIX if (CanSendTelemetry) { - s_sessionId = Guid.NewGuid().ToString(); - TelemetryConfiguration configuration = TelemetryConfiguration.CreateDefault(); - configuration.ConnectionString = "InstrumentationKey=" + _psCoreTelemetryKey; - - // Set this to true to reduce latency during development - configuration.TelemetryChannel.DeveloperMode = false; - - // Be sure to obscure any information about the client node name. - configuration.TelemetryInitializers.Add(new NameObscurerTelemetryInitializer()); - - s_telemetryClient = new TelemetryClient(configuration); - - // use a hashset when looking for module names, it should be quicker than a string comparison - s_knownModules = new HashSet<string>(StringComparer.OrdinalIgnoreCase) - { - "AADRM", - "activedirectory", - "adcsadministration", - "adcsdeployment", - "addsadministration", - "addsdeployment", - "adfs", - "adrms", - "adrmsadmin", - "agpm", - "AIShell", - "appbackgroundtask", - "applocker", - "appv", - "appvclient", - "appvsequencer", - "appvserver", - "appx", - "assignedaccess", - "Az", - "Az.Accounts", - "Az.Advisor", - "Az.Aks", - "Az.AlertsManagement", - "Az.AnalysisServices", - "Az.ApiManagement", - "Az.ApplicationInsights", - "Az.Attestation", - "Az.Automation", - "Az.Batch", - "Az.Billing", - "Az.Blueprint", - "Az.Cdn", - "Az.CognitiveServices", - "Az.Compute", - "Az.ContainerInstance", - "Az.ContainerRegistry", - "Az.DataBox", - "Az.DataFactory", - "Az.DataLakeAnalytics", - "Az.DataLakeStore", - "Az.DataMigration", - "Az.DataShare", - "Az.DeploymentManager", - "Az.DeviceProvisioningServices", - "Az.DevSpaces", - "Az.DevTestLabs", - "Az.Dns", - "Az.EventGrid", - "Az.EventHub", - "Az.FrontDoor", - "Az.GuestConfiguration", - "Az.HDInsight", - "Az.HealthcareApis", - "Az.IotCentral", - "Az.IotHub", - "Az.KeyVault", - "Az.Kusto", - "Az.LogicApp", - "Az.MachineLearning", - "Az.ManagedServiceIdentity", - "Az.ManagedServices", - "Az.ManagementPartner", - "Az.Maps", - "Az.MarketplaceOrdering", - "Az.Media", - "Az.MixedReality", - "Az.Monitor", - "Az.NetAppFiles", - "Az.Network", - "Az.NotificationHubs", - "Az.OperationalInsights", - "Az.Peering", - "Az.PolicyInsights", - "Az.PowerBIEmbedded", - "Az.PrivateDns", - "Az.RecoveryServices", - "Az.RedisCache", - "Az.Relay", - "Az.Reservations", - "Az.ResourceGraph", - "Az.Resources", - "Az.Search", - "Az.Security", - "Az.ServiceBus", - "Az.ServiceFabric", - "Az.SignalR", - "Az.Sql", - "Az.Storage", - "Az.StorageSync", - "Az.StorageTable", - "Az.StreamAnalytics", - "Az.Subscription", - "Az.Tools.Predictor", - "Az.TrafficManager", - "Az.Websites", - "Azs.Azurebridge.Admin", - "Azs.Backup.Admin", - "Azs.Commerce.Admin", - "Azs.Compute.Admin", - "Azs.Fabric.Admin", - "Azs.Gallery.Admin", - "Azs.Infrastructureinsights.Admin", - "Azs.Keyvault.Admin", - "Azs.Network.Admin", - "Azs.Storage.Admin", - "Azs.Subscriptions", - "Azs.Subscriptions.Admin", - "Azs.Update.Admin", - "AzStorageTable", - "Azure", - "Azure.AnalysisServices", - "Azure.Storage", - "AzureAD", - "AzureInformationProtection", - "AzureRM.Aks", - "AzureRM.AnalysisServices", - "AzureRM.ApiManagement", - "AzureRM.ApplicationInsights", - "AzureRM.Automation", - "AzureRM.Backup", - "AzureRM.Batch", - "AzureRM.Billing", - "AzureRM.Cdn", - "AzureRM.CognitiveServices", - "AzureRm.Compute", - "AzureRM.Compute.ManagedService", - "AzureRM.Consumption", - "AzureRM.ContainerInstance", - "AzureRM.ContainerRegistry", - "AzureRM.DataFactories", - "AzureRM.DataFactoryV2", - "AzureRM.DataLakeAnalytics", - "AzureRM.DataLakeStore", - "AzureRM.DataMigration", - "AzureRM.DeploymentManager", - "AzureRM.DeviceProvisioningServices", - "AzureRM.DevSpaces", - "AzureRM.DevTestLabs", - "AzureRm.Dns", - "AzureRM.EventGrid", - "AzureRM.EventHub", - "AzureRM.FrontDoor", - "AzureRM.HDInsight", - "AzureRm.Insights", - "AzureRM.IotCentral", - "AzureRM.IotHub", - "AzureRm.Keyvault", - "AzureRM.LocationBasedServices", - "AzureRM.LogicApp", - "AzureRM.MachineLearning", - "AzureRM.MachineLearningCompute", - "AzureRM.ManagedServiceIdentity", - "AzureRM.ManagementPartner", - "AzureRM.Maps", - "AzureRM.MarketplaceOrdering", - "AzureRM.Media", - "AzureRM.Network", - "AzureRM.NotificationHubs", - "AzureRM.OperationalInsights", - "AzureRM.PolicyInsights", - "AzureRM.PowerBIEmbedded", - "AzureRM.Profile", - "AzureRM.RecoveryServices", - "AzureRM.RecoveryServices.Backup", - "AzureRM.RecoveryServices.SiteRecovery", - "AzureRM.RedisCache", - "AzureRM.Relay", - "AzureRM.Reservations", - "AzureRM.ResourceGraph", - "AzureRM.Resources", - "AzureRM.Scheduler", - "AzureRM.Search", - "AzureRM.Security", - "AzureRM.ServerManagement", - "AzureRM.ServiceBus", - "AzureRM.ServiceFabric", - "AzureRM.SignalR", - "AzureRM.SiteRecovery", - "AzureRM.Sql", - "AzureRm.Storage", - "AzureRM.StorageSync", - "AzureRM.StreamAnalytics", - "AzureRM.Subscription", - "AzureRM.Subscription.Preview", - "AzureRM.Tags", - "AzureRM.TrafficManager", - "AzureRm.UsageAggregates", - "AzureRm.Websites", - "AzureRmStorageTable", - "bestpractices", - "bitlocker", - "bitstransfer", - "booteventcollector", - "branchcache", - "CimCmdlets", - "clusterawareupdating", - "CompatPowerShellGet", - "configci", - "ConfigurationManager", - "CompletionPredictor", - "DataProtectionManager", - "dcbqos", - "deduplication", - "defender", - "devicehealthattestation", - "dfsn", - "dfsr", - "dhcpserver", - "directaccessclient", - "directaccessclientcomponent", - "directaccessclientcomponents", - "dism", - "dnsclient", - "dnsserver", - "ElasticDatabaseJobs", - "EventTracingManagement", - "failoverclusters", - "fileserverresourcemanager", - "FIMAutomation", - "GPRegistryPolicy", - "grouppolicy", - "hardwarecertification", - "hcs", - "hgsattestation", - "hgsclient", - "hgsdiagnostics", - "hgskeyprotection", - "hgsserver", - "hnvdiagnostics", - "hostcomputeservice", - "hpc", - "HPC.ACM", - "HPC.ACM.API.PS", - "HPCPack2016", - "hyper-v", - "IISAdministration", - "international", - "ipamserver", - "iscsi", - "iscsitarget", - "ISE", - "kds", - "Microsoft.MBAM", - "Microsoft.MEDV", - "MgmtSvcAdmin", - "MgmtSvcConfig", - "MgmtSvcMySql", - "MgmtSvcSqlServer", - "Microsoft.AzureStack.ReadinessChecker", - "Microsoft.Crm.PowerShell", - "Microsoft.DiagnosticDataViewer", - "Microsoft.DirectoryServices.MetadirectoryServices.Config", - "Microsoft.Dynamics.Nav.Apps.Management", - "Microsoft.Dynamics.Nav.Apps.Tools", - "Microsoft.Dynamics.Nav.Ide", - "Microsoft.Dynamics.Nav.Management", - "Microsoft.Dynamics.Nav.Model.Tools", - "Microsoft.Dynamics.Nav.Model.Tools.Crm", - "Microsoft.EnterpriseManagement.Warehouse.Cmdlets", - "Microsoft.Medv.Administration.Commands.WorkspacePackager", - "Microsoft.PowerApps.Checker.PowerShell", - "Microsoft.PowerShell.Archive", - "Microsoft.PowerShell.ConsoleGuiTools", - "Microsoft.PowerShell.Core", - "Microsoft.PowerShell.Crescendo", - "Microsoft.PowerShell.Diagnostics", - "Microsoft.PowerShell.Host", - "Microsoft.PowerShell.LocalAccounts", - "Microsoft.PowerShell.Management", - "Microsoft.PowerShell.ODataUtils", - "Microsoft.PowerShell.Operation.Validation", - "Microsoft.PowerShell.PSAdapter", - "Microsoft.PowerShell.PSResourceGet", - "Microsoft.PowerShell.RemotingTools", - "Microsoft.PowerShell.SecretManagement", - "Microsoft.PowerShell.SecretStore", - "Microsoft.PowerShell.Security", - "Microsoft.PowerShell.TextUtility", - "Microsoft.PowerShell.Utility", - "Microsoft.SharePoint.Powershell", - "Microsoft.SystemCenter.ServiceManagementAutomation", - "Microsoft.Windows.ServerManager.Migration", - "Microsoft.WSMan.Management", - "Microsoft.Xrm.OnlineManagementAPI", - "Microsoft.Xrm.Tooling.CrmConnector.PowerShell", - "Microsoft.Xrm.Tooling.PackageDeployment", - "Microsoft.Xrm.Tooling.PackageDeployment.Powershell", - "Microsoft.Xrm.Tooling.Testing", - "MicrosoftPowerBIMgmt", - "MicrosoftPowerBIMgmt.Data", - "MicrosoftPowerBIMgmt.Profile", - "MicrosoftPowerBIMgmt.Reports", - "MicrosoftPowerBIMgmt.Workspaces", - "MicrosoftStaffHub", - "MicrosoftTeams", - "MIMPAM", - "mlSqlPs", - "MMAgent", - "MPIO", - "MsDtc", - "MSMQ", - "MSOnline", - "MSOnlineBackup", - "WmsCmdlets", - "WmsCmdlets3", - "NanoServerImageGenerator", - "NAVWebClientManagement", - "NetAdapter", - "NetConnection", - "NetEventPacketCapture", - "Netlbfo", - "Netldpagent", - "NetNat", - "Netqos", - "NetSecurity", - "NetSwitchtTeam", - "Nettcpip", - "Netwnv", - "NetworkConnectivity", - "NetworkConnectivityStatus", - "NetworkController", - "NetworkControllerDiagnostics", - "NetworkloadBalancingClusters", - "NetworkSwitchManager", - "NetworkTransition", - "NFS", - "NPS", - "OfficeWebapps", - "OperationsManager", - "PackageManagement", - "PartnerCenter", - "pcsvdevice", - "pef", - "Pester", - "pkiclient", - "platformidentifier", - "pnpdevice", - "PowerShellEditorServices", - "PowerShellGet", - "powershellwebaccess", - "printmanagement", - "ProcessMitigations", - "provisioning", - "PSDesiredStateConfiguration", - "PSDiagnostics", - "PSReadLine", - "PSScheduledJob", - "PSScriptAnalyzer", - "PSWorkflow", - "PSWorkflowUtility", - "RemoteAccess", - "RemoteDesktop", - "RemoteDesktopServices", - "ScheduledTasks", - "Secureboot", - "ServerCore", - "ServerManager", - "ServerManagerTasks", - "ServerMigrationcmdlets", - "ServiceFabric", - "Microsoft.Online.SharePoint.PowerShell", - "shieldedvmdatafile", - "shieldedvmprovisioning", - "shieldedvmtemplate", - "SkypeOnlineConnector", - "SkypeForBusinessHybridHealth", - "smbshare", - "smbwitness", - "smisconfig", - "softwareinventorylogging", - "SPFAdmin", - "Microsoft.SharePoint.MigrationTool.PowerShell", - "sqlps", - "SqlServer", - "StartLayout", - "StartScreen", - "Storage", - "StorageDsc", - "storageqos", - "Storagereplica", - "Storagespaces", - "Syncshare", - "System.Center.Service.Manager", - "TLS", - "TroubleshootingPack", - "TrustedPlatformModule", - "UEV", - "UpdateServices", - "UserAccessLogging", - "vamt", - "VirtualMachineManager", - "vpnclient", - "WasPSExt", - "WDAC", - "WDS", - "WebAdministration", - "WebAdministrationDsc", - "WebApplicationProxy", - "WebSites", - "Whea", - "WhiteboardAdmin", - "WindowsDefender", - "WindowsDefenderDsc", - "WindowsDeveloperLicense", - "WindowsDiagnosticData", - "WindowsErrorReporting", - "WindowServerRackup", - "WindowsSearch", - "WindowsServerBackup", - "WindowsUpdate", - "WinGetCommandNotFound", - "wsscmdlets", - "wsssetup", - "wsus", - "xActiveDirectory", - "xBitLocker", - "xDefender", - "xDhcpServer", - "xDismFeature", - "xDnsServer", - "xHyper-V", - "xHyper-VBackup", - "xPSDesiredStateConfiguration", - "xSmbShare", - "xSqlPs", - "xStorage", - "xWebAdministration", - "xWindowsUpdate", - }; - - // use a hashset when looking for module names, it should be quicker than a string comparison - s_knownModuleTags = new HashSet<string>(StringComparer.OrdinalIgnoreCase) - { - "CrescendoBuilt", - }; - - s_uniqueUserIdentifier = GetUniqueIdentifier().ToString(); - s_knownSubsystemNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase) - { - "Completion", - "General Feedback", - "Windows Package Manager - WinGet", - "Az Predictor" - }; + // Respect the diagnostics and feedback setting in Windows. + CanSendTelemetry = WindowsDataCollectionSetting.CanCollectDiagnostics(PlatformDataCollectionLevel.Enhanced); } - } +#endif - /// <summary> - /// Determine whether the environment variable is set and how. - /// </summary> - /// <param name="name">The name of the environment variable.</param> - /// <param name="defaultValue">If the environment variable is not set, use this as the default value.</param> - /// <returns>A boolean representing the value of the environment variable.</returns> - private static bool GetEnvironmentVariableAsBool(string name, bool defaultValue) - { - var str = Environment.GetEnvironmentVariable(name); - if (string.IsNullOrEmpty(str)) + if (!CanSendTelemetry) { - return defaultValue; + // Avoid the initialization work if we can't send telemetry. + return; } - var boolStr = str.AsSpan(); + s_sessionId = Guid.NewGuid().ToString(); + TelemetryConfiguration configuration = TelemetryConfiguration.CreateDefault(); + configuration.ConnectionString = "InstrumentationKey=" + _psCoreTelemetryKey; - if (boolStr.Length == 1) - { - if (boolStr[0] == '1') - { - return true; - } + // Set this to true to reduce latency during development + configuration.TelemetryChannel.DeveloperMode = false; - if (boolStr[0] == '0') - { - return false; - } - } + // Be sure to obscure any information about the client node name. + configuration.TelemetryInitializers.Add(new NameObscurerTelemetryInitializer()); - if (boolStr.Length == 3 && - (boolStr[0] == 'y' || boolStr[0] == 'Y') && - (boolStr[1] == 'e' || boolStr[1] == 'E') && - (boolStr[2] == 's' || boolStr[2] == 'S')) - { - return true; - } + s_telemetryClient = new TelemetryClient(configuration); - if (boolStr.Length == 2 && - (boolStr[0] == 'n' || boolStr[0] == 'N') && - (boolStr[1] == 'o' || boolStr[1] == 'O')) + // use a hashset when looking for module names, it should be quicker than a string comparison + s_knownModules = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { - return false; - } - - if (boolStr.Length == 4 && - (boolStr[0] == 't' || boolStr[0] == 'T') && - (boolStr[1] == 'r' || boolStr[1] == 'R') && - (boolStr[2] == 'u' || boolStr[2] == 'U') && - (boolStr[3] == 'e' || boolStr[3] == 'E')) + "AADRM", + "activedirectory", + "adcsadministration", + "adcsdeployment", + "addsadministration", + "addsdeployment", + "adfs", + "adrms", + "adrmsadmin", + "agpm", + "AIShell", + "appbackgroundtask", + "applocker", + "appv", + "appvclient", + "appvsequencer", + "appvserver", + "appx", + "assignedaccess", + "Az", + "Az.Accounts", + "Az.Advisor", + "Az.Aks", + "Az.AlertsManagement", + "Az.AnalysisServices", + "Az.ApiManagement", + "Az.ApplicationInsights", + "Az.Attestation", + "Az.Automation", + "Az.Batch", + "Az.Billing", + "Az.Blueprint", + "Az.Cdn", + "Az.CognitiveServices", + "Az.Compute", + "Az.ContainerInstance", + "Az.ContainerRegistry", + "Az.DataBox", + "Az.DataFactory", + "Az.DataLakeAnalytics", + "Az.DataLakeStore", + "Az.DataMigration", + "Az.DataShare", + "Az.DeploymentManager", + "Az.DeviceProvisioningServices", + "Az.DevSpaces", + "Az.DevTestLabs", + "Az.Dns", + "Az.EventGrid", + "Az.EventHub", + "Az.FrontDoor", + "Az.GuestConfiguration", + "Az.HDInsight", + "Az.HealthcareApis", + "Az.IotCentral", + "Az.IotHub", + "Az.KeyVault", + "Az.Kusto", + "Az.LogicApp", + "Az.MachineLearning", + "Az.ManagedServiceIdentity", + "Az.ManagedServices", + "Az.ManagementPartner", + "Az.Maps", + "Az.MarketplaceOrdering", + "Az.Media", + "Az.MixedReality", + "Az.Monitor", + "Az.NetAppFiles", + "Az.Network", + "Az.NotificationHubs", + "Az.OperationalInsights", + "Az.Peering", + "Az.PolicyInsights", + "Az.PowerBIEmbedded", + "Az.PrivateDns", + "Az.RecoveryServices", + "Az.RedisCache", + "Az.Relay", + "Az.Reservations", + "Az.ResourceGraph", + "Az.Resources", + "Az.Search", + "Az.Security", + "Az.ServiceBus", + "Az.ServiceFabric", + "Az.SignalR", + "Az.Sql", + "Az.Storage", + "Az.StorageSync", + "Az.StorageTable", + "Az.StreamAnalytics", + "Az.Subscription", + "Az.Tools.Predictor", + "Az.TrafficManager", + "Az.Websites", + "Azs.Azurebridge.Admin", + "Azs.Backup.Admin", + "Azs.Commerce.Admin", + "Azs.Compute.Admin", + "Azs.Fabric.Admin", + "Azs.Gallery.Admin", + "Azs.Infrastructureinsights.Admin", + "Azs.Keyvault.Admin", + "Azs.Network.Admin", + "Azs.Storage.Admin", + "Azs.Subscriptions", + "Azs.Subscriptions.Admin", + "Azs.Update.Admin", + "AzStorageTable", + "Azure", + "Azure.AnalysisServices", + "Azure.Storage", + "AzureAD", + "AzureInformationProtection", + "AzureRM.Aks", + "AzureRM.AnalysisServices", + "AzureRM.ApiManagement", + "AzureRM.ApplicationInsights", + "AzureRM.Automation", + "AzureRM.Backup", + "AzureRM.Batch", + "AzureRM.Billing", + "AzureRM.Cdn", + "AzureRM.CognitiveServices", + "AzureRm.Compute", + "AzureRM.Compute.ManagedService", + "AzureRM.Consumption", + "AzureRM.ContainerInstance", + "AzureRM.ContainerRegistry", + "AzureRM.DataFactories", + "AzureRM.DataFactoryV2", + "AzureRM.DataLakeAnalytics", + "AzureRM.DataLakeStore", + "AzureRM.DataMigration", + "AzureRM.DeploymentManager", + "AzureRM.DeviceProvisioningServices", + "AzureRM.DevSpaces", + "AzureRM.DevTestLabs", + "AzureRm.Dns", + "AzureRM.EventGrid", + "AzureRM.EventHub", + "AzureRM.FrontDoor", + "AzureRM.HDInsight", + "AzureRm.Insights", + "AzureRM.IotCentral", + "AzureRM.IotHub", + "AzureRm.Keyvault", + "AzureRM.LocationBasedServices", + "AzureRM.LogicApp", + "AzureRM.MachineLearning", + "AzureRM.MachineLearningCompute", + "AzureRM.ManagedServiceIdentity", + "AzureRM.ManagementPartner", + "AzureRM.Maps", + "AzureRM.MarketplaceOrdering", + "AzureRM.Media", + "AzureRM.Network", + "AzureRM.NotificationHubs", + "AzureRM.OperationalInsights", + "AzureRM.PolicyInsights", + "AzureRM.PowerBIEmbedded", + "AzureRM.Profile", + "AzureRM.RecoveryServices", + "AzureRM.RecoveryServices.Backup", + "AzureRM.RecoveryServices.SiteRecovery", + "AzureRM.RedisCache", + "AzureRM.Relay", + "AzureRM.Reservations", + "AzureRM.ResourceGraph", + "AzureRM.Resources", + "AzureRM.Scheduler", + "AzureRM.Search", + "AzureRM.Security", + "AzureRM.ServerManagement", + "AzureRM.ServiceBus", + "AzureRM.ServiceFabric", + "AzureRM.SignalR", + "AzureRM.SiteRecovery", + "AzureRM.Sql", + "AzureRm.Storage", + "AzureRM.StorageSync", + "AzureRM.StreamAnalytics", + "AzureRM.Subscription", + "AzureRM.Subscription.Preview", + "AzureRM.Tags", + "AzureRM.TrafficManager", + "AzureRm.UsageAggregates", + "AzureRm.Websites", + "AzureRmStorageTable", + "bestpractices", + "bitlocker", + "bitstransfer", + "booteventcollector", + "branchcache", + "CimCmdlets", + "clusterawareupdating", + "CompatPowerShellGet", + "configci", + "ConfigurationManager", + "CompletionPredictor", + "DataProtectionManager", + "dcbqos", + "deduplication", + "defender", + "devicehealthattestation", + "dfsn", + "dfsr", + "dhcpserver", + "directaccessclient", + "directaccessclientcomponent", + "directaccessclientcomponents", + "dism", + "dnsclient", + "dnsserver", + "ElasticDatabaseJobs", + "EventTracingManagement", + "failoverclusters", + "fileserverresourcemanager", + "FIMAutomation", + "GPRegistryPolicy", + "grouppolicy", + "hardwarecertification", + "hcs", + "hgsattestation", + "hgsclient", + "hgsdiagnostics", + "hgskeyprotection", + "hgsserver", + "hnvdiagnostics", + "hostcomputeservice", + "hpc", + "HPC.ACM", + "HPC.ACM.API.PS", + "HPCPack2016", + "hyper-v", + "IISAdministration", + "international", + "ipamserver", + "iscsi", + "iscsitarget", + "ISE", + "kds", + "Microsoft.MBAM", + "Microsoft.MEDV", + "MgmtSvcAdmin", + "MgmtSvcConfig", + "MgmtSvcMySql", + "MgmtSvcSqlServer", + "Microsoft.AzureStack.ReadinessChecker", + "Microsoft.Crm.PowerShell", + "Microsoft.DiagnosticDataViewer", + "Microsoft.DirectoryServices.MetadirectoryServices.Config", + "Microsoft.Dynamics.Nav.Apps.Management", + "Microsoft.Dynamics.Nav.Apps.Tools", + "Microsoft.Dynamics.Nav.Ide", + "Microsoft.Dynamics.Nav.Management", + "Microsoft.Dynamics.Nav.Model.Tools", + "Microsoft.Dynamics.Nav.Model.Tools.Crm", + "Microsoft.EnterpriseManagement.Warehouse.Cmdlets", + "Microsoft.Medv.Administration.Commands.WorkspacePackager", + "Microsoft.PowerApps.Checker.PowerShell", + "Microsoft.PowerShell.Archive", + "Microsoft.PowerShell.ConsoleGuiTools", + "Microsoft.PowerShell.Core", + "Microsoft.PowerShell.Crescendo", + "Microsoft.PowerShell.Diagnostics", + "Microsoft.PowerShell.Host", + "Microsoft.PowerShell.LocalAccounts", + "Microsoft.PowerShell.Management", + "Microsoft.PowerShell.ODataUtils", + "Microsoft.PowerShell.Operation.Validation", + "Microsoft.PowerShell.PSAdapter", + "Microsoft.PowerShell.PSResourceGet", + "Microsoft.PowerShell.RemotingTools", + "Microsoft.PowerShell.SecretManagement", + "Microsoft.PowerShell.SecretStore", + "Microsoft.PowerShell.Security", + "Microsoft.PowerShell.TextUtility", + "Microsoft.PowerShell.Utility", + "Microsoft.SharePoint.Powershell", + "Microsoft.SystemCenter.ServiceManagementAutomation", + "Microsoft.Windows.ServerManager.Migration", + "Microsoft.WSMan.Management", + "Microsoft.Xrm.OnlineManagementAPI", + "Microsoft.Xrm.Tooling.CrmConnector.PowerShell", + "Microsoft.Xrm.Tooling.PackageDeployment", + "Microsoft.Xrm.Tooling.PackageDeployment.Powershell", + "Microsoft.Xrm.Tooling.Testing", + "MicrosoftPowerBIMgmt", + "MicrosoftPowerBIMgmt.Data", + "MicrosoftPowerBIMgmt.Profile", + "MicrosoftPowerBIMgmt.Reports", + "MicrosoftPowerBIMgmt.Workspaces", + "MicrosoftStaffHub", + "MicrosoftTeams", + "MIMPAM", + "mlSqlPs", + "MMAgent", + "MPIO", + "MsDtc", + "MSMQ", + "MSOnline", + "MSOnlineBackup", + "WmsCmdlets", + "WmsCmdlets3", + "NanoServerImageGenerator", + "NAVWebClientManagement", + "NetAdapter", + "NetConnection", + "NetEventPacketCapture", + "Netlbfo", + "Netldpagent", + "NetNat", + "Netqos", + "NetSecurity", + "NetSwitchtTeam", + "Nettcpip", + "Netwnv", + "NetworkConnectivity", + "NetworkConnectivityStatus", + "NetworkController", + "NetworkControllerDiagnostics", + "NetworkloadBalancingClusters", + "NetworkSwitchManager", + "NetworkTransition", + "NFS", + "NPS", + "OfficeWebapps", + "OperationsManager", + "PackageManagement", + "PartnerCenter", + "pcsvdevice", + "pef", + "Pester", + "pkiclient", + "platformidentifier", + "pnpdevice", + "PowerShellEditorServices", + "PowerShellGet", + "powershellwebaccess", + "printmanagement", + "ProcessMitigations", + "provisioning", + "PSDesiredStateConfiguration", + "PSDiagnostics", + "PSReadLine", + "PSScheduledJob", + "PSScriptAnalyzer", + "PSWorkflow", + "PSWorkflowUtility", + "RemoteAccess", + "RemoteDesktop", + "RemoteDesktopServices", + "ScheduledTasks", + "Secureboot", + "ServerCore", + "ServerManager", + "ServerManagerTasks", + "ServerMigrationcmdlets", + "ServiceFabric", + "Microsoft.Online.SharePoint.PowerShell", + "shieldedvmdatafile", + "shieldedvmprovisioning", + "shieldedvmtemplate", + "SkypeOnlineConnector", + "SkypeForBusinessHybridHealth", + "smbshare", + "smbwitness", + "smisconfig", + "softwareinventorylogging", + "SPFAdmin", + "Microsoft.SharePoint.MigrationTool.PowerShell", + "sqlps", + "SqlServer", + "StartLayout", + "StartScreen", + "Storage", + "StorageDsc", + "storageqos", + "Storagereplica", + "Storagespaces", + "Syncshare", + "System.Center.Service.Manager", + "TLS", + "TroubleshootingPack", + "TrustedPlatformModule", + "UEV", + "UpdateServices", + "UserAccessLogging", + "vamt", + "VirtualMachineManager", + "vpnclient", + "WasPSExt", + "WDAC", + "WDS", + "WebAdministration", + "WebAdministrationDsc", + "WebApplicationProxy", + "WebSites", + "Whea", + "WhiteboardAdmin", + "WindowsDefender", + "WindowsDefenderDsc", + "WindowsDeveloperLicense", + "WindowsDiagnosticData", + "WindowsErrorReporting", + "WindowServerRackup", + "WindowsSearch", + "WindowsServerBackup", + "WindowsUpdate", + "WinGetCommandNotFound", + "wsscmdlets", + "wsssetup", + "wsus", + "xActiveDirectory", + "xBitLocker", + "xDefender", + "xDhcpServer", + "xDismFeature", + "xDnsServer", + "xHyper-V", + "xHyper-VBackup", + "xPSDesiredStateConfiguration", + "xSmbShare", + "xSqlPs", + "xStorage", + "xWebAdministration", + "xWindowsUpdate", + }; + + // use a hashset when looking for module names, it should be quicker than a string comparison + s_knownModuleTags = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { - return true; - } + "CrescendoBuilt", + }; - if (boolStr.Length == 5 && - (boolStr[0] == 'f' || boolStr[0] == 'F') && - (boolStr[1] == 'a' || boolStr[1] == 'A') && - (boolStr[2] == 'l' || boolStr[2] == 'L') && - (boolStr[3] == 's' || boolStr[3] == 'S') && - (boolStr[4] == 'e' || boolStr[4] == 'E')) + s_uniqueUserIdentifier = GetUniqueIdentifier().ToString(); + s_knownSubsystemNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { - return false; - } - - return defaultValue; + "Completion", + "General Feedback", + "Windows Package Manager - WinGet", + "Az Predictor" + }; } /// <summary> @@ -835,11 +782,11 @@ internal static void SendUseTelemetry(string featureName, string detail, double if (string.Compare(featureName, s_subsystemRegistration, true) == 0) { - ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.FeatureUse, string.Join(":", featureName, GetSubsystemName(detail)), value); + SendTelemetryMetric(TelemetryType.FeatureUse, string.Join(":", featureName, GetSubsystemName(detail)), value); } else { - ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.FeatureUse, string.Join(":", featureName, detail), value); + SendTelemetryMetric(TelemetryType.FeatureUse, string.Join(":", featureName, detail), value); } } @@ -855,7 +802,7 @@ internal static void SendExperimentalUseData(string featureName, string detail) return; } - ApplicationInsightsTelemetry.SendTelemetryMetric(TelemetryType.ExperimentalFeatureUse, string.Join(":", featureName, detail)); + SendTelemetryMetric(TelemetryType.ExperimentalFeatureUse, string.Join(":", featureName, detail)); } // Get the experimental feature name. If we can report it, we'll return the name of the feature, otherwise, we'll return "anonymous" @@ -1051,8 +998,7 @@ private static Guid GetUniqueIdentifier() // Try to get the unique id. If this returns false, we'll // create/recreate the telemetry.uuid file to persist for next startup. Guid id = Guid.Empty; - string uuidPath = Path.Join(Platform.CacheDirectory, "telemetry.uuid"); - if (TryGetIdentifier(uuidPath, out id)) + if (TryGetIdentifier(s_uuidPath, out id)) { return id; } @@ -1067,7 +1013,7 @@ private static Guid GetUniqueIdentifier() m.WaitOne(); try { - return CreateUniqueIdentifierAndFile(uuidPath); + return CreateUniqueIdentifierAndFile(s_uuidPath); } finally { diff --git a/src/System.Management.Automation/utils/WindowsDataCollectionSetting.cs b/src/System.Management.Automation/utils/WindowsDataCollectionSetting.cs new file mode 100644 index 00000000000..5f8b607550a --- /dev/null +++ b/src/System.Management.Automation/utils/WindowsDataCollectionSetting.cs @@ -0,0 +1,185 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#if !UNIX + +using System; +using System.Management.Automation.Internal; +using System.Management.Automation.Tracing; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.Marshalling; + +namespace Microsoft.PowerShell.Telemetry; + +internal enum PlatformDataCollectionLevel : int +{ + /// <summary> + /// Minimum — only security-related data. Enterprise/education editions only. + /// </summary> + Security = 0, + + /// <summary> + /// Device info, capabilities, and basic reliability data. + /// </summary> + Basic = 1, + + /// <summary> + /// More detailed usage and reliability data, including app/feature usage patterns. + /// Removed as a user-facing option in Windows 11 (collapsed into Full). + /// </summary> + Enhanced = 2, + + /// <summary> + /// All of the above plus advanced diagnostics data that can help Microsoft fix problems. + /// </summary> + Full = 3, +} + +/// <summary> +/// Minimal projection of <c>IInspectable</c>, the base interface for all WinRT objects. +/// Slots 3–5 in every WinRT interface vtable (after <c>IUnknown</c>'s QueryInterface/AddRef/Release). +/// </summary> +[GeneratedComInterface] +[Guid("AF86E2E0-B12D-4C6A-9C5A-D7AA65101E90")] +internal partial interface IInspectable +{ + void GetIids(out uint iidCount, out nint iids); + + nint GetRuntimeClassName(); + + int GetTrustLevel(); +} + +/// <summary> +/// Projection of the WinRT interface <c>Windows.System.Profile.IPlatformDiagnosticsAndUsageDataSettingsStatics</c> +/// (IID B6E24C1B-7B1C-4B32-8C62-A66597CE723A). +/// Vtable slots 6–9, following the three <c>IInspectable</c> slots. +/// </summary> +[GeneratedComInterface] +[Guid("B6E24C1B-7B1C-4B32-8C62-A66597CE723A")] +internal partial interface IPlatformDiagnosticsAndUsageDataSettingsStatics : IInspectable +{ + PlatformDataCollectionLevel GetCollectionLevel(); + + long AddCollectionLevelChanged(nint handler); + + void RemoveCollectionLevelChanged(long token); + + // WinRT marshals bool as a byte; use byte to avoid any MarshalAs ambiguity with the source generator. + byte CanCollectDiagnostics(PlatformDataCollectionLevel level); +} + +/// <summary> +/// Wraps <c>Windows.System.Profile.PlatformDiagnosticsAndUsageDataSettings</c> using compile-time COM interop +/// and source-generated P/Invoke. No extra runtime DLLs are required. +/// </summary> +internal static partial class WindowsDataCollectionSetting +{ + /// <summary> + /// Returns <see langword="true"/> if the device's diagnostic data collection policy permits collecting at or above <paramref name="level"/>. + /// </summary> + /// <param name="level">The minimum <see cref="PlatformDataCollectionLevel"/> to test against.</param> + internal static bool CanCollectDiagnostics(PlatformDataCollectionLevel level) + { + const string ClassName = "Windows.System.Profile.PlatformDiagnosticsAndUsageDataSettings"; + + // When initializing WinRT on the calling thread, use the multi-threaded apartment (MTA). + // This is to cover the case where PowerShell gets used in a thread-pool thread. + // See the doc at https://learn.microsoft.com/windows/win32/api/roapi/ne-roapi-ro_init_type + const int RO_INIT_MULTITHREADED = 1; + + // Return values for 'RoInitialize': + // - S_OK (0) - we successfully initialized; must call 'RoUninitialize'. + // - S_FALSE (1) - already initialized with the same apartment type; must still call 'RoUninitialize'. + // - RPC_E_CHANGED_MODE - already initialized with a different apartment type; WinRT still works, do NOT call 'RoUninitialize'. + const int RPC_E_CHANGED_MODE = unchecked((int)0x80010106); + + int initHr = -1; + nint hstring = default; + nint factoryPtr = default; + + try + { + // Initialize WinRT on the calling thread. 'RoGetActivationFactory' requires it. + initHr = RoInitialize(RO_INIT_MULTITHREADED); + if (initHr < 0 && initHr != RPC_E_CHANGED_MODE) + { + // The call to initialize the Windows Runtime failed. + // Throw an exception with the HRESULT error code to provide more context on the failure. + Marshal.ThrowExceptionForHR(initHr); + } + + Marshal.ThrowExceptionForHR( + WindowsCreateString(ClassName, (uint)ClassName.Length, out hstring)); + + Guid iid = new("B6E24C1B-7B1C-4B32-8C62-A66597CE723A"); + Marshal.ThrowExceptionForHR( + RoGetActivationFactory(hstring, ref iid, out factoryPtr)); + + var comWrappers = new StrategyBasedComWrappers(); + var comObject = comWrappers.GetOrCreateObjectForComInstance(factoryPtr, CreateObjectFlags.None); + var platformSetting = (IPlatformDiagnosticsAndUsageDataSettingsStatics)comObject; + + return platformSetting.CanCollectDiagnostics(level) != 0; + } + catch (Exception ex) + { + // Log any exceptions that occur during this process, but swallow them and return false to disable telemetry rather than crashing the product. + // This API is only used to gate telemetry collection, so failure should be non-fatal. + PSEtwLog.LogOperationalError( + PSEventId.Telemetry_Setting_Error, + PSOpcode.Exception, + PSTask.Telemetry, + PSKeyword.UseAlwaysOperational, + ex.GetType().FullName, + ex.Message, + ex.StackTrace); + + return false; + } + finally + { + if (factoryPtr != default) + { + Marshal.Release(factoryPtr); + } + + if (hstring != default) + { + _ = WindowsDeleteString(hstring); + } + + // Per COM documentation: Each successful call to 'RoInitialize' (including S_FALSE) + // must be balanced by a corresponding call to 'RoUninitialize'. + if (initHr >= 0) + { + RoUninitialize(); + } + } + } + + [LibraryImport("api-ms-win-core-winrt-string-l1-1-0.dll", StringMarshalling = StringMarshalling.Utf16)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + private static partial int WindowsCreateString( + string sourceString, + uint length, + out nint hstring); + + [LibraryImport("api-ms-win-core-winrt-string-l1-1-0.dll")] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + private static partial int WindowsDeleteString(nint hstring); + + [LibraryImport("api-ms-win-core-winrt-l1-1-0.dll")] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + private static partial int RoGetActivationFactory(nint activatableClassId, ref Guid iid, out nint factory); + + [LibraryImport("api-ms-win-core-winrt-l1-1-0.dll")] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + private static partial int RoInitialize(int initType); + + [LibraryImport("api-ms-win-core-winrt-l1-1-0.dll")] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + private static partial void RoUninitialize(); +} + +#endif diff --git a/src/System.Management.Automation/utils/perfCounters/CounterSetInstanceBase.cs b/src/System.Management.Automation/utils/perfCounters/CounterSetInstanceBase.cs index fc3f048b828..995b41fcda1 100644 --- a/src/System.Management.Automation/utils/perfCounters/CounterSetInstanceBase.cs +++ b/src/System.Management.Automation/utils/perfCounters/CounterSetInstanceBase.cs @@ -74,7 +74,6 @@ protected CounterSetInstanceBase(CounterSetRegistrarBase counterSetRegistrarInst /// But, if isNumerator is false, then a check is made on the input /// counter's type to ensure that denominator is indeed value for such a counter. /// </summary> - [SuppressMessage("Microsoft.Usage", "CA2233:OperationsShouldNotOverflow", Justification = "countId is validated as known denominator before it is incremented.")] protected bool RetrieveTargetCounterIdIfValid(int counterId, bool isNumerator, out int targetCounterId) { targetCounterId = counterId; diff --git a/src/System.Management.Automation/utils/perfCounters/CounterSetRegistrarBase.cs b/src/System.Management.Automation/utils/perfCounters/CounterSetRegistrarBase.cs index f9a08b76a87..1ea57a16912 100644 --- a/src/System.Management.Automation/utils/perfCounters/CounterSetRegistrarBase.cs +++ b/src/System.Management.Automation/utils/perfCounters/CounterSetRegistrarBase.cs @@ -95,7 +95,6 @@ public abstract class CounterSetRegistrarBase /// based on Provider Id, counterSetId, counterSetInstanceType, a collection /// with counters information and an optional counterSetName. /// </summary> - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] protected CounterSetRegistrarBase( Guid providerId, Guid counterSetId, @@ -220,7 +219,6 @@ public class PSCounterSetRegistrar : CounterSetRegistrarBase /// <summary> /// Constructor that creates an instance of PSCounterSetRegistrar. /// </summary> - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] public PSCounterSetRegistrar( Guid providerId, Guid counterSetId, diff --git a/src/System.Management.Automation/utils/perfCounters/PSPerfCountersMgr.cs b/src/System.Management.Automation/utils/perfCounters/PSPerfCountersMgr.cs index 761de0c6360..7a49a5c6f9f 100644 --- a/src/System.Management.Automation/utils/perfCounters/PSPerfCountersMgr.cs +++ b/src/System.Management.Automation/utils/perfCounters/PSPerfCountersMgr.cs @@ -162,7 +162,6 @@ public bool AddCounterSetInstance(CounterSetRegistrarBase counterSetRegistrarIns /// by 'stepAmount'. /// Otherwise, updates the denominator component by 'stepAmount'. /// </summary> - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] public bool UpdateCounterByValue( Guid counterSetId, int counterId, @@ -193,7 +192,6 @@ public bool UpdateCounterByValue( /// by 'stepAmount'. /// Otherwise, updates the denominator component by 'stepAmount'. /// </summary> - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] public bool UpdateCounterByValue( Guid counterSetId, string counterName, @@ -224,7 +222,6 @@ public bool UpdateCounterByValue( /// by 'stepAmount'. /// Otherwise, updates the denominator component by 'stepAmount'. /// </summary> - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] public bool UpdateCounterByValue( string counterSetName, int counterId, @@ -264,7 +261,6 @@ public bool UpdateCounterByValue( /// by 'stepAmount'. /// Otherwise, updates the denominator component by 'stepAmount'. /// </summary> - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] public bool UpdateCounterByValue( string counterSetName, string counterName, @@ -303,7 +299,6 @@ public bool UpdateCounterByValue( /// to 'counterValue'. /// Otherwise, updates the denominator component to 'counterValue'. /// </summary> - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] public bool SetCounterValue( Guid counterSetId, int counterId, @@ -334,7 +329,6 @@ public bool SetCounterValue( /// to 'counterValue'. /// Otherwise, updates the denominator component to 'counterValue'. /// </summary> - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] public bool SetCounterValue( Guid counterSetId, string counterName, @@ -365,7 +359,6 @@ public bool SetCounterValue( /// to 'counterValue'. /// Otherwise, updates the denominator component to 'counterValue'. /// </summary> - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] public bool SetCounterValue( string counterSetName, int counterId, @@ -404,7 +397,6 @@ public bool SetCounterValue( /// to 'counterValue'. /// Otherwise, updates the denominator component to 'counterValue'. /// </summary> - [SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] public bool SetCounterValue( string counterSetName, string counterName, diff --git a/src/System.Management.Automation/utils/tracing/EtwActivity.cs b/src/System.Management.Automation/utils/tracing/EtwActivity.cs index 433d90297ee..e00fd33efe2 100644 --- a/src/System.Management.Automation/utils/tracing/EtwActivity.cs +++ b/src/System.Management.Automation/utils/tracing/EtwActivity.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Diagnostics.Eventing; using System.Diagnostics; -using System.Runtime.InteropServices; using System.Diagnostics.CodeAnalysis; namespace System.Management.Automation.Tracing diff --git a/src/System.Management.Automation/utils/tracing/EtwActivityReverterMethodInvoker.cs b/src/System.Management.Automation/utils/tracing/EtwActivityReverterMethodInvoker.cs index d9d082879a6..8bd2ae3e620 100644 --- a/src/System.Management.Automation/utils/tracing/EtwActivityReverterMethodInvoker.cs +++ b/src/System.Management.Automation/utils/tracing/EtwActivityReverterMethodInvoker.cs @@ -21,7 +21,7 @@ internal class EtwActivityReverterMethodInvoker : public EtwActivityReverterMethodInvoker(IEtwEventCorrelator eventCorrelator) { - ArgumentNullException.ThrowIfNull(eventCorrelator); + ArgumentNullException.ThrowIfNull(eventCorrelator); _eventCorrelator = eventCorrelator; _invoker = DoInvoke; diff --git a/src/System.Management.Automation/utils/tracing/PSEtwLogProvider.cs b/src/System.Management.Automation/utils/tracing/PSEtwLogProvider.cs index f90bd92c6ed..625ba88a23b 100755 --- a/src/System.Management.Automation/utils/tracing/PSEtwLogProvider.cs +++ b/src/System.Management.Automation/utils/tracing/PSEtwLogProvider.cs @@ -132,7 +132,7 @@ internal override void LogCommandLifecycleEvent(Func<LogContext> getLogContext, } else { - // When state is Start log the CommandLine which has arguments for completeness. + // When state is Start log the CommandLine which has arguments for completeness. payload.AppendLine(StringUtil.Format(EtwLoggingStrings.CommandStateChange, logContext.CommandLine, newState.ToString())); } } diff --git a/src/System.Management.Automation/utils/tracing/Tracing.cs b/src/System.Management.Automation/utils/tracing/Tracing.cs index babbc5d9d63..1d4684de58a 100644 --- a/src/System.Management.Automation/utils/tracing/Tracing.cs +++ b/src/System.Management.Automation/utils/tracing/Tracing.cs @@ -10,7 +10,7 @@ namespace System.Management.Automation.Tracing /// <summary> /// Tracer. /// </summary> - public sealed partial class Tracer : System.Management.Automation.Tracing.EtwActivity + public sealed partial class Tracer : EtwActivity { /// <summary> /// DebugMessage. diff --git a/src/System.Management.Automation/utils/tracing/TracingGen.cs b/src/System.Management.Automation/utils/tracing/TracingGen.cs index 05f088b7f93..b7413d5a763 100644 --- a/src/System.Management.Automation/utils/tracing/TracingGen.cs +++ b/src/System.Management.Automation/utils/tracing/TracingGen.cs @@ -10,7 +10,7 @@ namespace System.Management.Automation.Tracing /// <summary> /// Tracer. /// </summary> - public sealed partial class Tracer : System.Management.Automation.Tracing.EtwActivity + public sealed partial class Tracer : EtwActivity { /// <summary> /// Critical level. @@ -37,7 +37,6 @@ public sealed partial class Tracer : System.Management.Automation.Tracing.EtwAct /// </summary> public const long KeywordAll = 0xFFFFFFFF; - private static readonly Guid providerId = Guid.Parse("a0c1853b-5c40-4b15-8766-3cf1c58f985a"); private static readonly EventDescriptor WriteTransferEventEvent; private static readonly EventDescriptor DebugMessageEvent; private static readonly EventDescriptor M3PAbortingWorkflowExecutionEvent; @@ -218,17 +217,6 @@ static Tracer() /// </summary> public Tracer() : base() { } - /// <summary> - /// Provider Guid. - /// </summary> - protected override Guid ProviderId - { - get - { - return providerId; - } - } - /// <summary> /// Transfer Event. /// </summary> diff --git a/src/TypeCatalogGen/TypeCatalogGen.cs b/src/TypeCatalogGen/TypeCatalogGen.cs index 3d4c0f21ed9..b0e604ec12d 100644 --- a/src/TypeCatalogGen/TypeCatalogGen.cs +++ b/src/TypeCatalogGen/TypeCatalogGen.cs @@ -470,4 +470,3 @@ internal TypeMetadata(string assemblyName, bool isTypeObsolete) } } } - diff --git a/src/TypeCatalogGen/TypeCatalogGen.csproj b/src/TypeCatalogGen/TypeCatalogGen.csproj index ffc3ff99986..83b21e178f5 100644 --- a/src/TypeCatalogGen/TypeCatalogGen.csproj +++ b/src/TypeCatalogGen/TypeCatalogGen.csproj @@ -2,7 +2,7 @@ <PropertyGroup> <Description>Generates CorePsTypeCatalog.cs given powershell.inc</Description> - <TargetFramework>net10.0</TargetFramework> + <TargetFramework>net11.0</TargetFramework> <TreatWarningsAsErrors>true</TreatWarningsAsErrors> <AssemblyName>TypeCatalogGen</AssemblyName> <OutputType>Exe</OutputType> diff --git a/src/powershell-unix/powershell-unix.csproj b/src/powershell-unix/powershell-unix.csproj index 802acf05e3a..20c61247d24 100644 --- a/src/powershell-unix/powershell-unix.csproj +++ b/src/powershell-unix/powershell-unix.csproj @@ -37,6 +37,10 @@ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory> </Content> + <Content Include="..\..\dsc\pwsh.profile.dsc.resource.json;..\..\dsc\pwsh.profile.resource.ps1"> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory> + </Content> </ItemGroup> <ItemGroup> diff --git a/src/powershell-win-core/powershell-win-core.csproj b/src/powershell-win-core/powershell-win-core.csproj index 5368518dd3c..dddbb915eca 100644 --- a/src/powershell-win-core/powershell-win-core.csproj +++ b/src/powershell-win-core/powershell-win-core.csproj @@ -29,7 +29,11 @@ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory> </Content> - <Content Include="..\..\LICENSE.txt;..\..\ThirdPartyNotices.txt;..\powershell-native\Install-PowerShellRemoting.ps1;..\PowerShell.Core.Instrumentation\PowerShell.Core.Instrumentation.man;..\PowerShell.Core.Instrumentation\RegisterManifest.ps1;..\..\assets\MicrosoftUpdate\RegisterMicrosoftUpdate.ps1;..\..\assets\GroupPolicy\PowerShellCoreExecutionPolicy.admx;..\..\assets\GroupPolicy\PowerShellCoreExecutionPolicy.adml;..\..\assets\GroupPolicy\InstallPSCorePolicyDefinitions.ps1"> + <Content Include="..\..\dsc\*"> + <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> + <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory> + </Content> + <Content Include="..\..\LICENSE.txt;..\..\ThirdPartyNotices.txt;..\powershell-native\Install-PowerShellRemoting.ps1;..\PowerShell.Core.Instrumentation\PowerShell.Core.Instrumentation.man;..\PowerShell.Core.Instrumentation\RegisterManifest.ps1;..\..\assets\GroupPolicy\PowerShellCoreExecutionPolicy.admx;..\..\assets\GroupPolicy\PowerShellCoreExecutionPolicy.adml;..\..\assets\GroupPolicy\InstallPSCorePolicyDefinitions.ps1"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory> </Content> diff --git a/src/powershell/Program.cs b/src/powershell/Program.cs index 70346a1d1ce..1de961674df 100644 --- a/src/powershell/Program.cs +++ b/src/powershell/Program.cs @@ -129,10 +129,7 @@ private static void AttemptExecPwshLogin(string[] args) // At this point, we are on macOS // Set up the mib array and the query for process maximum args size - Span<int> mib = stackalloc int[3]; - int mibLength = 2; - mib[0] = MACOS_CTL_KERN; - mib[1] = MACOS_KERN_ARGMAX; + Span<int> mib = [MACOS_CTL_KERN, MACOS_KERN_ARGMAX]; int size = IntPtr.Size / 2; int argmax = 0; @@ -141,7 +138,7 @@ private static void AttemptExecPwshLogin(string[] args) { fixed (int *mibptr = mib) { - ThrowOnFailure(nameof(argmax), SysCtl(mibptr, mibLength, &argmax, &size, IntPtr.Zero, 0)); + ThrowOnFailure(nameof(argmax), SysCtl(mibptr, mib.Length, &argmax, &size, IntPtr.Zero, 0)); } } @@ -155,16 +152,13 @@ private static void AttemptExecPwshLogin(string[] args) IntPtr executablePathPtr = IntPtr.Zero; try { - mib[0] = MACOS_CTL_KERN; - mib[1] = MACOS_KERN_PROCARGS2; - mib[2] = pid; - mibLength = 3; + mib = new int[] { MACOS_CTL_KERN, MACOS_KERN_PROCARGS2, pid }; unsafe { fixed (int *mibptr = mib) { - ThrowOnFailure(nameof(procargs), SysCtl(mibptr, mibLength, procargs.ToPointer(), &argmax, IntPtr.Zero, 0)); + ThrowOnFailure(nameof(procargs), SysCtl(mibptr, mib.Length, procargs.ToPointer(), &argmax, IntPtr.Zero, 0)); } // The memory block we're reading is a series of null-terminated strings diff --git a/test/Test.Common.props b/test/Test.Common.props index 3fafbbf8f85..8a5522e9eaa 100644 --- a/test/Test.Common.props +++ b/test/Test.Common.props @@ -6,8 +6,8 @@ <Company>Microsoft Corporation</Company> <Copyright>(c) Microsoft Corporation.</Copyright> - <TargetFramework>net10.0</TargetFramework> - <LangVersion>13.0</LangVersion> + <TargetFramework>net11.0</TargetFramework> + <LangVersion>preview</LangVersion> <TreatWarningsAsErrors>true</TreatWarningsAsErrors> <AllowUnsafeBlocks>true</AllowUnsafeBlocks> diff --git a/test/docker/networktest/DockerRemoting.Tests.ps1 b/test/docker/networktest/DockerRemoting.Tests.ps1 deleted file mode 100644 index 5f5a13c5b4c..00000000000 --- a/test/docker/networktest/DockerRemoting.Tests.ps1 +++ /dev/null @@ -1,89 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -$imageName = "remotetestimage" -Describe "Basic remoting test with docker" -tags @("Scenario","Slow"){ - BeforeAll { - $Timeout = 600 # 10 minutes to run these tests - $dockerimage = docker images --format "{{ .Repository }}" $imageName - if ( $dockerimage -ne $imageName ) { - $pending = $true - Write-Warning "Docker image '$imageName' not found, not running tests" - return - } - else { - $pending = $false - } - - # give the containers something to do, otherwise they will exit and be removed - Write-Verbose -Verbose "setting up docker container PowerShell server" - $server = docker run -d $imageName powershell -c Start-Sleep -Seconds $timeout - Write-Verbose -Verbose "setting up docker container PowerShell client" - $client = docker run -d $imageName powershell -c Start-Sleep -Seconds $timeout - - # get fullpath to installed core powershell - Write-Verbose -Verbose "Getting path to PowerShell" - $powershellcorepath = docker exec $server powershell -c "(get-childitem 'c:\program files\powershell\*\pwsh.exe').fullname" - if ( ! $powershellcorepath ) - { - $pending = $true - Write-Warning "Cannot find powershell executable, not running tests" - return - } - $powershellcoreversion = ($powershellcorepath -split "[\\/]")[-2] - # we will need the configuration of the core powershell endpoint - $powershellcoreConfiguration = "powershell.${powershellcoreversion}" - - # capture the hostnames of the containers which will be used by the tests - Write-Verbose -Verbose "getting server hostname" - $serverhostname = docker exec $server hostname - Write-Verbose -Verbose "getting client hostname" - $clienthostname = docker exec $client hostname - - # capture the versions of full and core PowerShell - Write-Verbose -Verbose "getting powershell full version" - $fullVersion = docker exec $client powershell -c "`$PSVersionTable.psversion.tostring()" - if ( ! $fullVersion ) - { - $pending = $true - Write-Warning "Cannot determine PowerShell full version, not running tests" - return - } - - Write-Verbose -Verbose "getting powershell version" - $coreVersion = docker exec $client "$powershellcorepath" -c "`$PSVersionTable.psversion.tostring()" - if ( ! $coreVersion ) - { - $pending = $true - Write-Warning "Cannot determine PowerShell version, not running tests" - return - } - } - - AfterAll { - # to debug, comment out the following - if ( $pending -eq $false ) { - docker rm -f $server - docker rm -f $client - } - } - - It "Full powershell can get correct remote powershell version" -Pending:$pending { - $result = docker exec $client powershell -c "`$ss = [security.securestring]::new(); '11aa!!AA'.ToCharArray() | ForEach-Object { `$ss.appendchar(`$_)}; `$c = [pscredential]::new('testuser',`$ss); `$ses=new-pssession $serverhostname -configurationname $powershellcoreConfiguration -auth basic -credential `$c; invoke-command -session `$ses { `$PSVersionTable.psversion.tostring() }" - $result | Should -Be $coreVersion - } - - It "Full powershell can get correct remote powershell full version" -Pending:$pending { - $result = docker exec $client powershell -c "`$ss = [security.securestring]::new(); '11aa!!AA'.ToCharArray() | ForEach-Object { `$ss.appendchar(`$_)}; `$c = [pscredential]::new('testuser',`$ss); `$ses=new-pssession $serverhostname -auth basic -credential `$c; invoke-command -session `$ses { `$PSVersionTable.psversion.tostring() }" - $result | Should -Be $fullVersion - } - - It "Core powershell can get correct remote powershell version" -Pending:$pending { - $result = docker exec $client "$powershellcorepath" -c "`$ss = [security.securestring]::new(); '11aa!!AA'.ToCharArray() | ForEach-Object { `$ss.appendchar(`$_)}; `$c = [pscredential]::new('testuser',`$ss); `$ses=new-pssession $serverhostname -configurationname $powershellcoreConfiguration -auth basic -credential `$c; invoke-command -session `$ses { `$PSVersionTable.psversion.tostring() }" - $result | Should -Be $coreVersion - } - - It "Core powershell can get correct remote powershell full version" -Pending:$pending { - $result = docker exec $client "$powershellcorepath" -c "`$ss = [security.securestring]::new(); '11aa!!AA'.ToCharArray() | ForEach-Object { `$ss.appendchar(`$_)}; `$c = [pscredential]::new('testuser',`$ss); `$ses=new-pssession $serverhostname -auth basic -credential `$c; invoke-command -session `$ses { `$PSVersionTable.psversion.tostring() }" - $result | Should -Be $fullVersion - } -} diff --git a/test/docker/networktest/Dockerfile b/test/docker/networktest/Dockerfile deleted file mode 100644 index d558b139246..00000000000 --- a/test/docker/networktest/Dockerfile +++ /dev/null @@ -1,24 +0,0 @@ -# escape=` -FROM mcr.microsoft.com/windows/servercore:ltsc2022@sha256:5d09ffa90d91a46e2fe7652b0a37cbf5217f34a819c3d71cbe635dae8226061b - -SHELL ["pwsh.exe","-command"] - -# the source msi should change on a daily basis -# the destination should not change -ADD PSCore.msi /PSCore.msi - -# the msipath (PSCore.msi) below will need to change based on the daily package -# set up for basic auth -# install-powershellremoting will restart winrm service -RUN new-LocalUser -Name testuser -password (ConvertTo-SecureString 11aa!!AA -asplaintext -force); ` - add-localgroupmember -group administrators -member testuser; ` - set-item wsman:/localhost/service/auth/basic $true; ` - set-item WSMan:/localhost/client/trustedhosts "*" -force; ` - set-item wsman:/localhost/service/AllowUnencrypted $true; ` - set-item WSMan:/localhost/client/AllowUnencrypted $true; ` - Start-Process -FilePath msiexec.exe -ArgumentList '-qn', ` - '-i c:\PSCore.msi','-log c:\PSCore-install.log','-norestart' -wait ; ` - $psexec = get-item -path ${ENV:ProgramFiles}/powershell/*/pwsh.exe; ` - $corehome = $psexec.directory.fullname; ` - & $psexec Install-PowerShellRemoting.ps1; ` - remove-item -force c:\PSCore.msi diff --git a/test/fuzzing/FuzzingApp/Program.cs b/test/fuzzing/FuzzingApp/Program.cs new file mode 100644 index 00000000000..4e309ffc468 --- /dev/null +++ b/test/fuzzing/FuzzingApp/Program.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using SharpFuzz; + +namespace FuzzTests +{ + public static class Program + { + public static void Main(string[] args) + { + FuzzTargetMethod(args); + } + + public static void FuzzTargetMethod(string[] args) + { + if (args == null) + { + Console.WriteLine("args was null"); + args = Array.Empty<string>(); + } + + try + { + Fuzzer.LibFuzzer.Run(Target.ExtractToken); + } + catch (ArgumentNullException nex) + { + Console.WriteLine($"ArgumentNullException in main: {nex.Message}"); + Console.WriteLine($"Stack Trace: {nex.StackTrace}"); + Environment.Exit(1); + } + catch (Exception ex) + { + Console.WriteLine($"Exception in main: {ex.Message}"); + Console.WriteLine($"Exception type: {ex.GetType()}"); + Console.WriteLine($"Stack Trace: {ex.StackTrace}"); + Environment.Exit(1); + } + } + } +} diff --git a/test/fuzzing/FuzzingApp/Target.cs b/test/fuzzing/FuzzingApp/Target.cs new file mode 100644 index 00000000000..d82e17e8702 --- /dev/null +++ b/test/fuzzing/FuzzingApp/Target.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Management.Automation.Remoting; + +namespace FuzzTests +{ + public static class Target + { + public static void ExtractToken(ReadOnlySpan<byte> tokenResponse) + { + RemoteSessionHyperVSocketClient.ExtractToken(tokenResponse); + } + } +} diff --git a/test/fuzzing/FuzzingApp/powershell-fuzz-tests.csproj b/test/fuzzing/FuzzingApp/powershell-fuzz-tests.csproj new file mode 100644 index 00000000000..7ee82c34cae --- /dev/null +++ b/test/fuzzing/FuzzingApp/powershell-fuzz-tests.csproj @@ -0,0 +1,25 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <Import Project="..\..\Test.Common.props"/> + + <PropertyGroup> + <Description>PowerShell Fuzzing</Description> + <AssemblyName>powershell-fuzz-tests</AssemblyName> + <OutputType>Exe</OutputType> + </PropertyGroup> + + <PropertyGroup> + <DelaySign>true</DelaySign> + <AssemblyOriginatorKeyFile>../../../src/signing/visualstudiopublic.snk</AssemblyOriginatorKeyFile> + <SignAssembly>true</SignAssembly> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="../../../src/System.Management.Automation/System.Management.Automation.csproj" /> + </ItemGroup> + + <ItemGroup> + <PackageReference Include="SharpFuzz" Version="2.2.0" /> + </ItemGroup> + +</Project> diff --git a/test/fuzzing/inputs/maxinput b/test/fuzzing/inputs/maxinput new file mode 100644 index 00000000000..6773a896b3c --- /dev/null +++ b/test/fuzzing/inputs/maxinput @@ -0,0 +1 @@ +TOKEN abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/= \ No newline at end of file diff --git a/test/fuzzing/inputs/mininput b/test/fuzzing/inputs/mininput new file mode 100644 index 00000000000..c9b91c737c2 --- /dev/null +++ b/test/fuzzing/inputs/mininput @@ -0,0 +1 @@ +TOKEN TQ \ No newline at end of file diff --git a/test/fuzzing/runFuzzer.ps1 b/test/fuzzing/runFuzzer.ps1 new file mode 100644 index 00000000000..5cb63393c4c --- /dev/null +++ b/test/fuzzing/runFuzzer.ps1 @@ -0,0 +1,65 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +<# +1. libfuzzer-dotnet-windows.exe can be installed from https://github.com/Metalnem/libfuzzer-dotnet/releases + +2. sharpfuzz can be installed with dotnet-tool: + > dotnet tool install --global SharpFuzz.CommandLine --version 2.2.0 + +Usage: sharpfuzz [path-to-assembly] [prefix ...] + +path-to-assembly: + The path to an assembly .dll file to instrument. + +prefix: + The class or the namespace to instrument. + If not present, all types in the assembly will be instrumented. + At least one prefix is required when instrumenting System.Private.CoreLib. + +Examples: + sharpfuzz Newtonsoft.Json.dll + sharpfuzz System.Private.CoreLib.dll System.Number + sharpfuzz System.Private.CoreLib.dll System.DateTimeFormat System.DateTimeParse +#> + +param ( + [string]$libFuzzer = "$PSScriptRoot\libfuzzer-dotnet-windows.exe", + [string]$project = "$PSScriptRoot\FuzzingApp\powershell-fuzz-tests.csproj", + [string]$corpus = "$PSScriptRoot\inputs", + [string]$command = "sharpfuzz.exe" +) + +Set-StrictMode -Version Latest + +$outputDir = "$PSScriptRoot\out" + +if (Test-Path $outputDir) { + Remove-Item -Recurse -Force $outputDir +} + +Write-Host "dotnet publish $project -c Debug -o $outputDir" +dotnet publish $project -c Debug -o $outputDir +Write-Host "build completed" + +$projectName = (Get-Item $project).BaseName +$projectDll = "$projectName.dll" +$project = Join-Path $outputDir $projectDll +$smaDllPath = Join-Path $outputDir "System.Management.Automation.dll" + +## Instrument the specific class within the test assembly. +Write-Host "instrumenting: $project" +## !NOTE! If you instrument the class that defines "Main", it will fail. +& $command $project "FuzzTests.Target" +Write-Host "done instrumenting $project" + +## Instrument any other assemblies that need to be tested. +Write-Host "instrumenting: $smaDllPath" +& $command $smaDllPath "System.Management.Automation.Remoting.RemoteSessionHyperVSocketClient" +Write-Host "done instrumenting: $smaDllPath" + +$outputPath = Join-Path $outputDir "output.txt" + +Write-Host "launching fuzzer on $project" +Write-Host "$libFuzzer --target_path=dotnet --target_arg=$project $corpus" +& $libFuzzer --target_path=dotnet --target_arg=$project $corpus -max_len=1024 2>&1 | Tee-Object -FilePath $outputPath diff --git a/test/infrastructure/ciModule.Tests.ps1 b/test/infrastructure/ciModule.Tests.ps1 new file mode 100644 index 00000000000..b7320ff49b7 --- /dev/null +++ b/test/infrastructure/ciModule.Tests.ps1 @@ -0,0 +1,246 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# NOTE: This test file tests the Test-MergeConflictMarker function which detects Git merge conflict markers. +# IMPORTANT: Do NOT use here-strings or literal conflict markers (e.g., "<<<<<<<", "=======", ">>>>>>>") +# in this file, as they will trigger conflict marker detection in CI pipelines. +# Instead, use string multiplication (e.g., '<' * 7) to dynamically generate these markers at runtime. + +Describe "Test-MergeConflictMarker" { + BeforeAll { + # Import the module + Import-Module "$PSScriptRoot/../../tools/ci.psm1" -Force + + # Create a temporary test workspace + $script:testWorkspace = Join-Path $TestDrive "workspace" + New-Item -ItemType Directory -Path $script:testWorkspace -Force | Out-Null + + # Create temporary output files + $script:testOutputPath = Join-Path $TestDrive "outputs.txt" + $script:testSummaryPath = Join-Path $TestDrive "summary.md" + } + + AfterEach { + # Clean up test files after each test + if (Test-Path $script:testWorkspace) { + Get-ChildItem $script:testWorkspace -File -ErrorAction SilentlyContinue | Remove-Item -Force -ErrorAction SilentlyContinue + } + Remove-Item $script:testOutputPath -Force -ErrorAction SilentlyContinue + Remove-Item $script:testSummaryPath -Force -ErrorAction SilentlyContinue + } + + Context "When no files are provided" { + It "Should handle empty file array gracefully" { + # The function now accepts empty arrays to handle cases like delete-only PRs + $emptyArray = @() + Test-MergeConflictMarker -File $emptyArray -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath + + $outputs = Get-Content $script:testOutputPath + $outputs | Should -Contain "files-checked=0" + $outputs | Should -Contain "conflicts-found=0" + + $summary = Get-Content $script:testSummaryPath -Raw + $summary | Should -Match "No Files to Check" + } + } + + Context "When files have no conflicts" { + It "Should pass for clean files" { + $testFile = Join-Path $script:testWorkspace "clean.txt" + "This is a clean file" | Out-File $testFile -Encoding utf8 + + Test-MergeConflictMarker -File @("clean.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath + + $outputs = Get-Content $script:testOutputPath + $outputs | Should -Contain "files-checked=1" + $outputs | Should -Contain "conflicts-found=0" + + $summary = Get-Content $script:testSummaryPath -Raw + $summary | Should -Match "No Conflicts Found" + } + } + + Context "When files have conflict markers" { + It "Should detect <<<<<<< marker" { + $testFile = Join-Path $script:testWorkspace "conflict1.txt" + "Some content`n" + ('<' * 7) + " HEAD`nConflicting content" | Out-File $testFile -Encoding utf8 + + { Test-MergeConflictMarker -File @("conflict1.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath } | Should -Throw + + $outputs = Get-Content $script:testOutputPath + $outputs | Should -Contain "files-checked=1" + $outputs | Should -Contain "conflicts-found=1" + } + + It "Should detect ======= marker" { + $testFile = Join-Path $script:testWorkspace "conflict2.txt" + "Some content`n" + ('=' * 7) + "`nMore content" | Out-File $testFile -Encoding utf8 + + { Test-MergeConflictMarker -File @("conflict2.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath } | Should -Throw + } + + It "Should detect >>>>>>> marker" { + $testFile = Join-Path $script:testWorkspace "conflict3.txt" + "Some content`n" + ('>' * 7) + " branch-name`nMore content" | Out-File $testFile -Encoding utf8 + + { Test-MergeConflictMarker -File @("conflict3.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath } | Should -Throw + } + + It "Should detect multiple markers in one file" { + $testFile = Join-Path $script:testWorkspace "conflict4.txt" + $content = "Some content`n" + ('<' * 7) + " HEAD`nContent A`n" + ('=' * 7) + "`nContent B`n" + ('>' * 7) + " branch`nMore content" + $content | Out-File $testFile -Encoding utf8 + + { Test-MergeConflictMarker -File @("conflict4.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath } | Should -Throw + + $summary = Get-Content $script:testSummaryPath -Raw + $summary | Should -Match "Conflicts Detected" + $summary | Should -Match "conflict4.txt" + } + + It "Should detect conflicts in multiple files" { + $testFile1 = Join-Path $script:testWorkspace "conflict5.txt" + ('<' * 7) + " HEAD" | Out-File $testFile1 -Encoding utf8 + + $testFile2 = Join-Path $script:testWorkspace "conflict6.txt" + ('=' * 7) | Out-File $testFile2 -Encoding utf8 + + { Test-MergeConflictMarker -File @("conflict5.txt", "conflict6.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath } | Should -Throw + + $outputs = Get-Content $script:testOutputPath + $outputs | Should -Contain "files-checked=2" + $outputs | Should -Contain "conflicts-found=2" + } + } + + Context "When markers are not at line start" { + It "Should not detect markers in middle of line" { + $testFile = Join-Path $script:testWorkspace "notconflict.txt" + "This line has <<<<<<< in the middle" | Out-File $testFile -Encoding utf8 + + Test-MergeConflictMarker -File @("notconflict.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath + + $outputs = Get-Content $script:testOutputPath + $outputs | Should -Contain "conflicts-found=0" + } + + It "Should not detect markers with wrong number of characters" { + $testFile = Join-Path $script:testWorkspace "wrongcount.txt" + ('<' * 6) + " Only 6`n" + ('<' * 8) + " 8 characters" | Out-File $testFile -Encoding utf8 + + Test-MergeConflictMarker -File @("wrongcount.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath + + $outputs = Get-Content $script:testOutputPath + $outputs | Should -Contain "conflicts-found=0" + } + } + + Context "When handling special file scenarios" { + It "Should skip non-existent files" { + Test-MergeConflictMarker -File @("nonexistent.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath + + $outputs = Get-Content $script:testOutputPath + $outputs | Should -Contain "files-checked=0" + } + + It "Should handle absolute paths" { + $testFile = Join-Path $script:testWorkspace "absolute.txt" + "Clean content" | Out-File $testFile -Encoding utf8 + + Test-MergeConflictMarker -File @($testFile) -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath + + $outputs = Get-Content $script:testOutputPath + $outputs | Should -Contain "conflicts-found=0" + } + + It "Should handle mixed relative and absolute paths" { + $testFile1 = Join-Path $script:testWorkspace "relative.txt" + "Clean" | Out-File $testFile1 -Encoding utf8 + + $testFile2 = Join-Path $script:testWorkspace "absolute.txt" + "Clean" | Out-File $testFile2 -Encoding utf8 + + Test-MergeConflictMarker -File @("relative.txt", $testFile2) -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath + + $outputs = Get-Content $script:testOutputPath + $outputs | Should -Contain "files-checked=2" + $outputs | Should -Contain "conflicts-found=0" + } + } + + Context "When summary and output generation" { + It "Should generate proper GitHub Actions outputs format" { + $testFile = Join-Path $script:testWorkspace "test.txt" + "Clean file" | Out-File $testFile -Encoding utf8 + + Test-MergeConflictMarker -File @("test.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath + + $outputs = Get-Content $script:testOutputPath + $outputs | Where-Object {$_ -match "^files-checked=\d+$"} | Should -Not -BeNullOrEmpty + $outputs | Where-Object {$_ -match "^conflicts-found=\d+$"} | Should -Not -BeNullOrEmpty + } + + It "Should generate markdown summary with conflict details" { + $testFile = Join-Path $script:testWorkspace "marked.txt" + $content = "Line 1`n" + ('<' * 7) + " HEAD`nLine 3`n" + ('=' * 7) + "`nLine 5" + $content | Out-File $testFile -Encoding utf8 + + { Test-MergeConflictMarker -File @("marked.txt") -WorkspacePath $script:testWorkspace -OutputPath $script:testOutputPath -SummaryPath $script:testSummaryPath } | Should -Throw + + $summary = Get-Content $script:testSummaryPath -Raw + $summary | Should -Match "# Merge Conflict Marker Check Results" + $summary | Should -Match "marked.txt" + $summary | Should -Match "\| Line \| Marker \|" + } + } +} + +Describe "Install-CIPester" { + BeforeAll { + # Import the module + Import-Module "$PSScriptRoot/../../tools/ci.psm1" -Force + } + + Context "When checking function exists" { + It "Should export Install-CIPester function" { + $function = Get-Command Install-CIPester -ErrorAction SilentlyContinue + $function | Should -Not -BeNullOrEmpty + $function.ModuleName | Should -Be 'ci' + } + + It "Should have expected parameters" { + $function = Get-Command Install-CIPester + $function.Parameters.Keys | Should -Contain 'MinimumVersion' + $function.Parameters.Keys | Should -Contain 'MaximumVersion' + $function.Parameters.Keys | Should -Contain 'Force' + } + + It "Should accept version parameters" { + $function = Get-Command Install-CIPester + $function.Parameters['MinimumVersion'].ParameterType.Name | Should -Be 'String' + $function.Parameters['MaximumVersion'].ParameterType.Name | Should -Be 'String' + $function.Parameters['Force'].ParameterType.Name | Should -Be 'SwitchParameter' + } + } + + Context "When validating real execution" { + # These tests only run in CI where we can safely install/test Pester + + It "Should successfully run without errors when Pester exists" { + if (!$env:CI) { + Set-ItResult -Skipped -Because "Test requires CI environment to safely install Pester" + } + + { Install-CIPester -ErrorAction Stop } | Should -Not -Throw + } + + It "Should accept custom version parameters" { + if (!$env:CI) { + Set-ItResult -Skipped -Because "Test requires CI environment to safely install Pester" + } + + { Install-CIPester -MinimumVersion '4.0.0' -MaximumVersion '5.99.99' -ErrorAction Stop } | Should -Not -Throw + } + } +} + diff --git a/test/packaging/linux/package-validation.tests.ps1 b/test/packaging/linux/package-validation.tests.ps1 new file mode 100644 index 00000000000..3b961120f2f --- /dev/null +++ b/test/packaging/linux/package-validation.tests.ps1 @@ -0,0 +1,118 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe "Linux Package Name Validation" { + BeforeAll { + # Determine artifacts directory (GitHub Actions or Azure DevOps) + $artifactsDir = if ($env:GITHUB_ACTIONS -eq 'true') { + "$env:GITHUB_WORKSPACE/../packages" + } else { + $env:SYSTEM_ARTIFACTSDIRECTORY + } + + if (-not $artifactsDir) { + throw "Artifacts directory not found. GITHUB_WORKSPACE or SYSTEM_ARTIFACTSDIRECTORY must be set." + } + + Write-Verbose "Artifacts directory: $artifactsDir" -Verbose + } + + Context "RPM Package Names" { + It "Should have valid RPM package names" { + $rpmPackages = Get-ChildItem -Path $artifactsDir -Recurse -Filter *.rpm -ErrorAction SilentlyContinue + + $rpmPackages.Count | Should -BeGreaterThan 0 -Because "At least one RPM package should exist in the artifacts directory" + + $invalidPackages = @() + # Regex pattern for valid RPM package names. + # Breakdown: + # ^powershell\- : Starts with 'powershell-' + # (preview-|lts-)? : Optionally 'preview-' or 'lts-' + # \d+\.\d+\.\d+ : Version number (e.g., 7.6.0) + # (_[a-z]*\.\d+)? : Optional underscore, letters, dot, and digits (e.g., _alpha.1) + # -1\. : Literal '-1.' + # (preview\.\d+\.)? : Optional 'preview.' and digits, followed by a dot + # (rh|cm)\. : Either 'rh.' or 'cm.' + # (x86_64|aarch64)\.rpm$ : Architecture and file extension + $rpmPackageNamePattern = 'powershell\-(preview-|lts-)?\d+\.\d+\.\d+(_[a-z]*\.\d+)?-1\.(preview\.\d+\.)?(rh|cm)\.(x86_64|aarch64)\.rpm' + + foreach ($package in $rpmPackages) { + if ($package.Name -notmatch $rpmPackageNamePattern) { + $invalidPackages += "$($package.Name) is not a valid RPM package name" + Write-Warning "$($package.Name) is not a valid RPM package name" + } + } + + if ($invalidPackages.Count -gt 0) { + throw ($invalidPackages | Out-String) + } + } + } + + Context "DEB Package Names" { + It "Should have valid DEB package names" { + $debPackages = Get-ChildItem -Path $artifactsDir -Recurse -Filter *.deb -ErrorAction SilentlyContinue + + $debPackages.Count | Should -BeGreaterThan 0 -Because "At least one DEB package should exist in the artifacts directory" + + $invalidPackages = @() + # Regex pattern for valid DEB package names. + # Valid examples: + # - powershell-preview_7.6.0-preview.6-1.deb_amd64.deb + # - powershell-lts_7.4.13-1.deb_amd64.deb + # - powershell_7.4.13-1.deb_amd64.deb + # - powershell_7.6.0-1.deb_arm64.deb + # Breakdown: + # ^powershell : Starts with 'powershell' + # (-preview|-lts)? : Optionally '-preview' or '-lts' + # _\d+\.\d+\.\d+ : Underscore followed by version number (e.g., _7.6.0) + # (-[a-z]+\.\d+)? : Optional dash, letters, dot, and digits (e.g., -preview.6) + # -1 : Literal '-1' + # \.deb_ : Literal '.deb_' + # (amd64|arm64) : Architecture + # \.deb$ : File extension + $debPackageNamePattern = '^powershell(-preview|-lts)?_\d+\.\d+\.\d+(-[a-z]+\.\d+)?-1\.deb_(amd64|arm64)\.deb$' + + foreach ($package in $debPackages) { + if ($package.Name -notmatch $debPackageNamePattern) { + $invalidPackages += "$($package.Name) is not a valid DEB package name" + Write-Warning "$($package.Name) is not a valid DEB package name" + } + } + + if ($invalidPackages.Count -gt 0) { + throw ($invalidPackages | Out-String) + } + } + } + + Context "Tar.Gz Package Names" { + It "Should have valid tar.gz package names" { + $tarPackages = Get-ChildItem -Path $artifactsDir -Recurse -Filter *.tar.gz -ErrorAction SilentlyContinue + + $tarPackages.Count | Should -BeGreaterThan 0 -Because "At least one tar.gz package should exist in the artifacts directory" + + $invalidPackages = @() + foreach ($package in $tarPackages) { + # Pattern matches: powershell-7.6.0-preview.6-linux-x64.tar.gz or powershell-7.6.0-linux-x64.tar.gz + # Also matches various runtime configurations + if ($package.Name -notmatch 'powershell-(lts-)?\d+\.\d+\.\d+\-([a-z]*.\d+\-)?(linux|osx|linux-musl)+\-(x64\-fxdependent|x64|arm32|arm64|x64\-musl-noopt\-fxdependent)\.(tar\.gz)') { + $invalidPackages += "$($package.Name) is not a valid tar.gz package name" + Write-Warning "$($package.Name) is not a valid tar.gz package name" + } + } + + if ($invalidPackages.Count -gt 0) { + throw ($invalidPackages | Out-String) + } + } + } + + Context "Package Existence" { + It "Should find at least one package in artifacts directory" { + $allPackages = Get-ChildItem -Path $artifactsDir -Recurse -Include *.rpm, *.tar.gz, *.deb -ErrorAction SilentlyContinue + + $allPackages.Count | Should -BeGreaterThan 0 -Because "At least one package should exist in the artifacts directory" + } + } +} diff --git a/test/packaging/macos/package-validation.tests.ps1 b/test/packaging/macos/package-validation.tests.ps1 new file mode 100644 index 00000000000..945ffea6f7a --- /dev/null +++ b/test/packaging/macos/package-validation.tests.ps1 @@ -0,0 +1,186 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe "Verify macOS Package" { + BeforeAll { + Write-Verbose "In Describe BeforeAll" -Verbose + Import-Module $PSScriptRoot/../../../build.psm1 + + # Find the macOS package + $packagePath = $env:PACKAGE_FOLDER + if (-not $packagePath) { + $packagePath = Get-Location + } + + Write-Verbose "Looking for package in: $packagePath" -Verbose + $package = Get-ChildItem -Path $packagePath -Filter "*.pkg" -ErrorAction SilentlyContinue | Select-Object -First 1 + + if (-not $package) { + Write-Warning "No .pkg file found in $packagePath" + } else { + Write-Verbose "Found package: $($package.FullName)" -Verbose + } + + # Set up test directories + $script:package = $package + $script:expandDir = $null + $script:payloadDir = $null + $script:extractedFiles = @() + + if ($package) { + # Use TestDrive for temporary directories - pkgutil will create the expand directory + $script:expandDir = Join-Path "TestDrive:" -ChildPath "package-contents-test" + $expandDirResolved = (Resolve-Path "TestDrive:").ProviderPath + $script:expandDir = Join-Path $expandDirResolved -ChildPath "package-contents-test" + + Write-Verbose "Expanding package to: $($script:expandDir)" -Verbose + # pkgutil will create the directory itself, so don't pre-create it + Start-NativeExecution { + pkgutil --expand $package.FullName $script:expandDir + } + + # Extract the payload to verify files + $script:payloadDir = Join-Path "TestDrive:" -ChildPath "package-payload-test" + $payloadDirResolved = (Resolve-Path "TestDrive:").ProviderPath + $script:payloadDir = Join-Path $payloadDirResolved -ChildPath "package-payload-test" + + # Create payload directory since cpio needs it + if (-not (Test-Path $script:payloadDir)) { + $null = New-Item -ItemType Directory -Path $script:payloadDir -Force + } + + $componentPkg = Get-ChildItem -Path $script:expandDir -Filter "*.pkg" -Recurse | Select-Object -First 1 + if ($componentPkg) { + Write-Verbose "Extracting payload from: $($componentPkg.FullName)" -Verbose + Push-Location $script:payloadDir + try { + $payloadFile = Join-Path $componentPkg.FullName "Payload" + Get-Content -Path $payloadFile -Raw -AsByteStream | & cpio -i 2>&1 | Out-Null + } finally { + Pop-Location + } + } + + # Get all extracted files for verification + $script:extractedFiles = Get-ChildItem -Path $script:payloadDir -Recurse -ErrorAction SilentlyContinue + Write-Verbose "Extracted $($script:extractedFiles.Count) files" -Verbose + } + } + + AfterAll { + # TestDrive automatically cleans up, but we can ensure cleanup happens + # No manual cleanup needed as TestDrive handles it + } + + Context "Package existence and structure" { + It "Package file should exist" { + $script:package | Should -Not -BeNullOrEmpty -Because "A .pkg file should be created" + $script:package.Extension | Should -Be ".pkg" + } + + It "Package name should follow correct naming convention" { + $script:package | Should -Not -BeNullOrEmpty + + # Regex pattern for valid macOS PKG package names. + # This pattern matches the validation used in release-validate-packagenames.yml + # Valid examples: + # - powershell-7.4.13-osx-x64.pkg (Stable release) + # - powershell-7.6.0-preview.6-osx-x64.pkg (Preview version string) + # - powershell-7.4.13-rebuild.5-osx-arm64.pkg (Rebuild version) + # - powershell-lts-7.4.13-osx-arm64.pkg (LTS package) + $pkgPackageNamePattern = '^powershell-(lts-)?\d+\.\d+\.\d+\-([a-z]*.\d+\-)?osx\-(x64|arm64)\.pkg$' + + $script:package.Name | Should -Match $pkgPackageNamePattern -Because "Package name should follow the standard naming convention" + } + + It "Package name should NOT use x86_64 with underscores" { + $script:package | Should -Not -BeNullOrEmpty + + $script:package.Name | Should -Not -Match 'x86_64' -Because "Package should use 'x64' not 'x86_64' (with underscores) for compatibility" + } + + It "Package should expand successfully" { + $script:expandDir | Should -Exist + Get-ChildItem -Path $script:expandDir | Should -Not -BeNullOrEmpty + } + + It "Package should have a component package" { + $componentPkg = Get-ChildItem -Path $script:expandDir -Filter "*.pkg" -Recurse -ErrorAction SilentlyContinue + $componentPkg | Should -Not -BeNullOrEmpty -Because "Package should contain a component.pkg" + } + + It "Payload should extract successfully" { + $script:payloadDir | Should -Exist + $script:extractedFiles | Should -Not -BeNullOrEmpty -Because "Package payload should contain files" + } + } + + Context "Required files in package" { + BeforeAll { + $expectedFilePatterns = @{ + "PowerShell executable" = "usr/local/microsoft/powershell/*/pwsh" + "PowerShell symlink in /usr/local/bin" = "usr/local/bin/pwsh*" + "Man page" = "usr/local/share/man/man1/pwsh*.gz" + "Launcher application plist" = "Applications/PowerShell*.app/Contents/Info.plist" + } + + $testCases = @() + foreach ($key in $expectedFilePatterns.Keys) { + $testCases += @{ + Description = $key + Pattern = $expectedFilePatterns[$key] + } + } + + $script:testCases = $testCases + } + + It "Should contain <Description>" -TestCases $script:testCases { + param($Description, $Pattern) + + $found = $script:extractedFiles | Where-Object { $_.FullName -like "*$Pattern*" } + $found | Should -Not -BeNullOrEmpty -Because "$Description should exist in the package at path matching '$Pattern'" + } + } + + Context "PowerShell binary verification" { + It "PowerShell executable should be executable" { + $pwshBinary = $script:extractedFiles | Where-Object { $_.FullName -like "*/pwsh" -and $_.FullName -like "*/microsoft/powershell/*" } + $pwshBinary | Should -Not -BeNullOrEmpty + + # Check if file has executable permissions (on Unix-like systems) + if ($IsLinux -or $IsMacOS) { + $permissions = (Get-Item $pwshBinary[0].FullName).UnixFileMode + # Executable bit should be set + $permissions.ToString() | Should -Match 'x' -Because "pwsh binary should have execute permissions" + } + } + } + + Context "Launcher application" { + It "Launcher app should have proper bundle structure" { + $plistFile = $script:extractedFiles | Where-Object { $_.FullName -like "*PowerShell*.app/Contents/Info.plist" } + $plistFile | Should -Not -BeNullOrEmpty + + # Verify the bundle has required components + $appPath = Split-Path (Split-Path $plistFile[0].FullName -Parent) -Parent + $macOSDir = Join-Path $appPath "Contents/MacOS" + $resourcesDir = Join-Path $appPath "Contents/Resources" + + Test-Path $macOSDir | Should -Be $true -Because "App bundle should have Contents/MacOS directory" + Test-Path $resourcesDir | Should -Be $true -Because "App bundle should have Contents/Resources directory" + } + + It "Launcher script should exist and be executable" { + $launcherScript = $script:extractedFiles | Where-Object { + $_.FullName -like "*PowerShell*.app/Contents/MacOS/PowerShell.sh" + } + $launcherScript | Should -Not -BeNullOrEmpty -Because "Launcher script should exist" + + if ($IsLinux -or $IsMacOS) { + $permissions = (Get-Item $launcherScript[0].FullName).UnixFileMode + $permissions.ToString() | Should -Match 'x' -Because "Launcher script should have execute permissions" + } + } + } +} diff --git a/test/packaging/packaging.tests.ps1 b/test/packaging/packaging.tests.ps1 new file mode 100644 index 00000000000..a7d322205bc --- /dev/null +++ b/test/packaging/packaging.tests.ps1 @@ -0,0 +1,64 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe "Packaging Module Functions" { + BeforeAll { + Import-Module $PSScriptRoot/../../build.psm1 -Force + Import-Module $PSScriptRoot/../../tools/packaging/packaging.psm1 -Force + } + + Context "Test-IsPreview function" { + It "Should return True for preview versions" { + Test-IsPreview -Version "7.6.0-preview.6" | Should -Be $true + Test-IsPreview -Version "7.5.0-rc.1" | Should -Be $true + } + + It "Should return False for stable versions" { + Test-IsPreview -Version "7.6.0" | Should -Be $false + Test-IsPreview -Version "7.5.0" | Should -Be $false + } + + It "Should return False for LTS builds regardless of version string" { + Test-IsPreview -Version "7.6.0-preview.6" -IsLTS | Should -Be $false + Test-IsPreview -Version "7.5.0" -IsLTS | Should -Be $false + } + } + + Context "Get-MacOSPackageIdentifierInfo function (New-MacOSPackage logic)" { + It "Should detect preview builds and return preview identifier" { + $result = Get-MacOSPackageIdentifierInfo -Version "7.6.0-preview.6" -LTS:$false + + $result.IsPreview | Should -Be $true + $result.PackageIdentifier | Should -Be "com.microsoft.powershell-preview" + } + + It "Should detect stable builds and return stable identifier" { + $result = Get-MacOSPackageIdentifierInfo -Version "7.6.0" -LTS:$false + + $result.IsPreview | Should -Be $false + $result.PackageIdentifier | Should -Be "com.microsoft.powershell" + } + + It "Should treat LTS builds as stable even with preview version string" { + $result = Get-MacOSPackageIdentifierInfo -Version "7.4.0-preview.1" -LTS:$true + + $result.IsPreview | Should -Be $false + $result.PackageIdentifier | Should -Be "com.microsoft.powershell" + } + + It "Should NOT use package name for preview detection (bug fix verification) - <Name>" -TestCases @( + @{ Version = "7.6.0-preview.6"; Name = "Preview" } + @{ Version = "7.6.0-rc.1"; Name = "RC" } + ) { + # This test verifies the fix for issue #26673 + # The bug was using ($Name -like '*-preview') which always returned false + # because preview builds use Name="powershell" not "powershell-preview" + param($Version) + + # The CORRECT logic (the fix): uses version string + $result = Get-MacOSPackageIdentifierInfo -Version $Version -LTS:$false + $result.IsPreview | Should -Be $true -Because "Version string correctly identifies preview" + $result.PackageIdentifier | Should -Be "com.microsoft.powershell-preview" + } + } +} diff --git a/test/perf/benchmarks/Engine.Compiler.cs b/test/perf/benchmarks/Engine.Compiler.cs index 4f9dbab88a9..68385847f5b 100644 --- a/test/perf/benchmarks/Engine.Compiler.cs +++ b/test/perf/benchmarks/Engine.Compiler.cs @@ -61,7 +61,7 @@ public void GlobalSetup() // believe that there is no need to run many ops in each iteration. However, the subsequent runs // of this method is much faster than the first run, and this causes 'MinIterationTime' warnings // to our benchmarks and make the benchmark results not reliable. - // Calling this method once in 'GlobalSetup' is a workaround. + // Calling this method once in 'GlobalSetup' is a workaround. // See https://github.com/dotnet/BenchmarkDotNet/issues/837#issuecomment-828600157 CompileFunction(); } diff --git a/test/perf/benchmarks/assets/compiler.test.ps1 b/test/perf/benchmarks/assets/compiler.test.ps1 index 5105ae9b408..be731373036 100644 --- a/test/perf/benchmarks/assets/compiler.test.ps1 +++ b/test/perf/benchmarks/assets/compiler.test.ps1 @@ -2238,26 +2238,6 @@ function Start-PSPackage { } } } - "msi" { - $TargetArchitecture = "x64" - if ($Runtime -match "-x86") { - $TargetArchitecture = "x86" - } - Write-Verbose "TargetArchitecture = $TargetArchitecture" -Verbose - - $Arguments = @{ - ProductNameSuffix = $NameSuffix - ProductSourcePath = $Source - ProductVersion = $Version - AssetsPath = "$RepoRoot\assets" - ProductTargetArchitecture = $TargetArchitecture - Force = $Force - } - - if ($PSCmdlet.ShouldProcess("Create MSI Package")) { - New-MSIPackage @Arguments - } - } "msix" { $Arguments = @{ ProductNameSuffix = $NameSuffix diff --git a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/BenchmarkDotNet.Extensions.csproj b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/BenchmarkDotNet.Extensions.csproj index c2310231c36..92c3f13d290 100644 --- a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/BenchmarkDotNet.Extensions.csproj +++ b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/BenchmarkDotNet.Extensions.csproj @@ -6,8 +6,8 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="BenchmarkDotNet" Version="0.15.2" /> - <PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.15.2" /> + <PackageReference Include="BenchmarkDotNet" Version="0.15.8" /> + <PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.15.8" /> </ItemGroup> <ItemGroup> diff --git a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/CommandLineOptions.cs b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/CommandLineOptions.cs index aa60c7cc2e6..48856632317 100644 --- a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/CommandLineOptions.cs +++ b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/CommandLineOptions.cs @@ -37,7 +37,7 @@ public static List<string> ParseAndRemoveStringsParameter(List<string> argsList, if (parameterIndex + 1 < argsList.Count) { - while (parameterIndex + 1 < argsList.Count && !argsList[parameterIndex + 1].StartsWith("-")) + while (parameterIndex + 1 < argsList.Count && !argsList[parameterIndex + 1].StartsWith('-')) { // remove each filter string and stop when we get to the next argument flag parameterValue.Add(argsList[parameterIndex + 1]); diff --git a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/Extensions.cs b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/Extensions.cs index 7d0631b896b..9bc477bc4fe 100644 --- a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/Extensions.cs +++ b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/Extensions.cs @@ -13,7 +13,7 @@ public static class SummaryExtensions public static int ToExitCode(this IEnumerable<Summary> summaries) { // an empty summary means that initial filtering and validation did not allow to run - if (!summaries.Any()) + if (!summaries.Any()) return 1; // if anything has failed, it's an error @@ -23,4 +23,4 @@ public static int ToExitCode(this IEnumerable<Summary> summaries) return 0; } } -} \ No newline at end of file +} diff --git a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/MandatoryCategoryValidator.cs b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/MandatoryCategoryValidator.cs index 7b3b2d38f3b..6f84b3f5767 100644 --- a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/MandatoryCategoryValidator.cs +++ b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/MandatoryCategoryValidator.cs @@ -32,4 +32,4 @@ public IEnumerable<ValidationError> Validate(ValidationParameters validationPara $"{benchmarkId} does not belong to one of the mandatory categories: {string.Join(", ", _mandatoryCategories)}. Use [BenchmarkCategory(Categories.$)]") ); } -} \ No newline at end of file +} diff --git a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/PartitionFilter.cs b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/PartitionFilter.cs index 16ae22f3167..d7089f6f5a8 100644 --- a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/PartitionFilter.cs +++ b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/PartitionFilter.cs @@ -10,13 +10,13 @@ public class PartitionFilter : IFilter private readonly int? _partitionsCount; private readonly int? _partitionIndex; // indexed from 0 private int _counter = 0; - + public PartitionFilter(int? partitionCount, int? partitionIndex) { _partitionsCount = partitionCount; _partitionIndex = partitionIndex; } - + public bool Predicate(BenchmarkCase benchmarkCase) { if (!_partitionsCount.HasValue || !_partitionIndex.HasValue) @@ -24,4 +24,4 @@ public bool Predicate(BenchmarkCase benchmarkCase) return _counter++ % _partitionsCount.Value == _partitionIndex.Value; // will return true only for benchmarks that belong to it’s partition } -} \ No newline at end of file +} diff --git a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/TooManyTestCasesValidator.cs b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/TooManyTestCasesValidator.cs index b001f603dcb..cd9c3a424ce 100644 --- a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/TooManyTestCasesValidator.cs +++ b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/TooManyTestCasesValidator.cs @@ -14,9 +14,9 @@ namespace BenchmarkDotNet.Extensions public class TooManyTestCasesValidator : IValidator { private const int Limit = 16; - + public static readonly IValidator FailOnError = new TooManyTestCasesValidator(); - + public bool TreatsWarningsAsErrors => true; public IEnumerable<ValidationError> Validate(ValidationParameters validationParameters) @@ -30,4 +30,4 @@ public IEnumerable<ValidationError> Validate(ValidationParameters validationPara benchmarkCase: group.First())); } } -} \ No newline at end of file +} diff --git a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/UniqueArgumentsValidator.cs b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/UniqueArgumentsValidator.cs index 7bfab8445cb..0309e1e9065 100644 --- a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/UniqueArgumentsValidator.cs +++ b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/UniqueArgumentsValidator.cs @@ -30,7 +30,7 @@ private class BenchmarkArgumentsComparer : IEqualityComparer<BenchmarkCase> { public bool Equals(BenchmarkCase x, BenchmarkCase y) => Enumerable.SequenceEqual( - x.Parameters.Items.Select(argument => argument.Value), + x.Parameters.Items.Select(argument => argument.Value), y.Parameters.Items.Select(argument => argument.Value)); public int GetHashCode(BenchmarkCase obj) diff --git a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/ValuesGenerator.cs b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/ValuesGenerator.cs index 87bf6d82d8d..85f5d98af59 100644 --- a/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/ValuesGenerator.cs +++ b/test/perf/dotnet-tools/BenchmarkDotNet.Extensions/ValuesGenerator.cs @@ -33,7 +33,7 @@ public static T[] ArrayOfUniqueValues<T>(int count) // of random-sized memory by BDN engine T[] result = new T[count]; - var random = new Random(Seed); + var random = new Random(Seed); var uniqueValues = new HashSet<T>(); @@ -49,12 +49,12 @@ public static T[] ArrayOfUniqueValues<T>(int count) return result; } - + public static T[] Array<T>(int count) { var result = new T[count]; - var random = new Random(Seed); + var random = new Random(Seed); if (typeof(T) == typeof(byte) || typeof(T) == typeof(sbyte)) { @@ -145,4 +145,4 @@ private static Guid GenerateRandomGuid(Random random) return new Guid(bytes); } } -} \ No newline at end of file +} diff --git a/test/perf/dotnet-tools/Reporting/Counter.cs b/test/perf/dotnet-tools/Reporting/Counter.cs index 3952369c312..f97f0771b99 100644 --- a/test/perf/dotnet-tools/Reporting/Counter.cs +++ b/test/perf/dotnet-tools/Reporting/Counter.cs @@ -20,4 +20,4 @@ public class Counter public IList<double> Results { get; set; } } -} \ No newline at end of file +} diff --git a/test/perf/dotnet-tools/Reporting/Os.cs b/test/perf/dotnet-tools/Reporting/Os.cs index 692a75ee7c5..760142d3137 100644 --- a/test/perf/dotnet-tools/Reporting/Os.cs +++ b/test/perf/dotnet-tools/Reporting/Os.cs @@ -12,4 +12,4 @@ public class Os public string Name { get; set; } } -} \ No newline at end of file +} diff --git a/test/perf/dotnet-tools/Reporting/Reporter.cs b/test/perf/dotnet-tools/Reporting/Reporter.cs index 9291d6f36eb..d99ecfaf47c 100644 --- a/test/perf/dotnet-tools/Reporting/Reporter.cs +++ b/test/perf/dotnet-tools/Reporting/Reporter.cs @@ -44,7 +44,7 @@ public static Reporter CreateReporter(IEnvironment environment = null) { ret.Init(); } - + return ret; } @@ -66,7 +66,7 @@ private void Init() { var split = kvp.Split('='); run.Configurations.Add(split[0], split[1]); - } + } } os = new Os() @@ -91,7 +91,7 @@ private void Init() public string GetJson() { if (!InLab) - { + { return null; } var jsonobj = new @@ -122,7 +122,7 @@ public string WriteResultTable() ret.AppendLine($"{LeftJustify("Metric", counterWidth)}|{LeftJustify("Average",resultWidth)}|{LeftJustify("Min", resultWidth)}|{LeftJustify("Max",resultWidth)}"); ret.AppendLine($"{new String('-', counterWidth)}|{new String('-', resultWidth)}|{new String('-', resultWidth)}|{new String('-', resultWidth)}"); - + ret.AppendLine(Print(defaultCounter, counterWidth, resultWidth)); foreach(var counter in topCounters) { diff --git a/test/perf/dotnet-tools/Reporting/Reporting.csproj b/test/perf/dotnet-tools/Reporting/Reporting.csproj index 72a831b9406..b11b5e36ec4 100644 --- a/test/perf/dotnet-tools/Reporting/Reporting.csproj +++ b/test/perf/dotnet-tools/Reporting/Reporting.csproj @@ -6,7 +6,7 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> + <PackageReference Include="Newtonsoft.Json" Version="13.0.4" /> <PackageReference Include="Microsoft.DotNet.PlatformAbstractions" Version="5.0.0-preview.5.20278.1" /> </ItemGroup> diff --git a/test/perf/dotnet-tools/ResultsComparer/CommandLineOptions.cs b/test/perf/dotnet-tools/ResultsComparer/CommandLineOptions.cs index d3ad01be95b..90a439f66cc 100644 --- a/test/perf/dotnet-tools/ResultsComparer/CommandLineOptions.cs +++ b/test/perf/dotnet-tools/ResultsComparer/CommandLineOptions.cs @@ -51,4 +51,4 @@ public static IEnumerable<Example> Examples } } } -} \ No newline at end of file +} diff --git a/test/perf/dotnet-tools/ResultsComparer/DataTransferContracts.cs b/test/perf/dotnet-tools/ResultsComparer/DataTransferContracts.cs index c3399ea4333..94511488efd 100644 --- a/test/perf/dotnet-tools/ResultsComparer/DataTransferContracts.cs +++ b/test/perf/dotnet-tools/ResultsComparer/DataTransferContracts.cs @@ -130,4 +130,4 @@ public class BdnResult public HostEnvironmentInfo HostEnvironmentInfo { get; set; } public List<Benchmark> Benchmarks { get; set; } } -} \ No newline at end of file +} diff --git a/test/perf/dotnet-tools/ResultsComparer/Program.cs b/test/perf/dotnet-tools/ResultsComparer/Program.cs index 68615f61c78..a0c14e0057a 100644 --- a/test/perf/dotnet-tools/ResultsComparer/Program.cs +++ b/test/perf/dotnet-tools/ResultsComparer/Program.cs @@ -191,7 +191,7 @@ private static void ExportToCsv((string id, Benchmark baseResult, Benchmark diff private static void ExportToXml((string id, Benchmark baseResult, Benchmark diffResult, EquivalenceTestConclusion conclusion)[] notSame, FileInfo xmlPath) { if (xmlPath == null) - { + { Console.WriteLine("No file given"); return; } diff --git a/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj b/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj index d9d36f3b3c5..ff3f14d5a0d 100644 --- a/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj +++ b/test/perf/dotnet-tools/ResultsComparer/ResultsComparer.csproj @@ -3,13 +3,13 @@ <OutputType>Exe</OutputType> <TargetFrameworks>$(PERFLAB_TARGET_FRAMEWORKS)</TargetFrameworks> <TargetFramework Condition="'$(TargetFrameworks)' == ''">net5.0</TargetFramework> - <LangVersion>13.0</LangVersion> + <LangVersion>preview</LangVersion> </PropertyGroup> <ItemGroup> <PackageReference Include="CommandLineParser" Version="2.9.1" /> <PackageReference Include="MarkdownLog.NS20" Version="0.10.1" /> - <PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> - <PackageReference Include="BenchmarkDotNet" Version="0.15.2" /> - <PackageReference Include="Perfolizer" Version="0.5.3" /> + <PackageReference Include="Newtonsoft.Json" Version="13.0.4" /> + <PackageReference Include="BenchmarkDotNet" Version="0.15.8" /> + <PackageReference Include="Perfolizer" Version="0.7.1" /> </ItemGroup> </Project> diff --git a/test/powershell/Host/ConsoleHost.Tests.ps1 b/test/powershell/Host/ConsoleHost.Tests.ps1 index ec0e80de5c5..534cce2d356 100644 --- a/test/powershell/Host/ConsoleHost.Tests.ps1 +++ b/test/powershell/Host/ConsoleHost.Tests.ps1 @@ -386,6 +386,33 @@ export $envVarName='$guid' } } + Context "-SettingsFile Commandline switch set 'PSModulePath'" { + + BeforeAll { + $CustomSettingsFile = Join-Path -Path $TestDrive -ChildPath 'powershell.test.json' + $mPath1 = Join-Path $PSHOME 'Modules' + $mPath2 = Join-Path $TestDrive 'NonExist' + $pathSep = [System.IO.Path]::PathSeparator + + ## Use multiple paths in the setting. + $ModulePath = "${mPath1}${pathSep}${mPath2}".Replace('\', "\\") + Set-Content -Path $CustomSettingsfile -Value "{`"Microsoft.PowerShell:ExecutionPolicy`":`"Unrestricted`", `"PSModulePath`": `"$ModulePath`" }" -ErrorAction Stop + } + + It "Verify PowerShell PSModulePath should contain paths from config file" { + $psModulePath = & $powershell -NoProfile -SettingsFile $CustomSettingsFile -Command '$env:PSModulePath' + + ## $mPath1 already exists in the value of env PSModulePath, so it won't be added again. + $index = $psModulePath.IndexOf("${mPath1}${pathSep}", [System.StringComparison]::OrdinalIgnoreCase) + $index | Should -BeGreaterThan 0 + $index += $mPath1.Length + $psModulePath.IndexOf($mPath1, $index, [System.StringComparison]::OrdinalIgnoreCase) | Should -BeExactly -1 + + ## $mPath2 should be added at the index position 0. + $psModulePath.StartsWith("${mPath2}${pathSep}", [System.StringComparison]::OrdinalIgnoreCase) | Should -BeTrue + } + } + Context "Pipe to/from powershell" { BeforeAll { if ($null -ne $PSStyle) { @@ -985,7 +1012,12 @@ public static WINDOWPLACEMENT GetPlacement(IntPtr hwnd) { WINDOWPLACEMENT placement = new WINDOWPLACEMENT(); placement.length = Marshal.SizeOf(placement); - GetWindowPlacement(hwnd, ref placement); + + if (!GetWindowPlacement(hwnd, ref placement)) + { + throw new System.ComponentModel.Win32Exception(); + } + return placement; } @@ -1021,7 +1053,7 @@ public enum ShowWindowCommands : int $global:PSDefaultParameterValues = $defaultParamValues } - It "-WindowStyle <WindowStyle> should work on Windows" -TestCases @( + It "-WindowStyle <WindowStyle> should work on Windows" -Pending -TestCases @( @{WindowStyle="Normal"}, @{WindowStyle="Minimized"}, @{WindowStyle="Maximized"} # hidden doesn't work in CI/Server Core @@ -1203,4 +1235,19 @@ Describe 'TERM env var' -Tag CI { $env:NO_COLOR = $null } } + + It 'No_COLOR should be respected for redirected output' { + $psi = [System.Diagnostics.ProcessStartInfo] @{ + FileName = 'pwsh' + # Pass a command that succeeds and normally produces colored output, and one that produces error output. + Arguments = '-NoProfile -Command Get-Item .; Get-Content \nosuch123' + # Redirect (capture) both stdout and stderr. + RedirectStandardOutput = $true + RedirectStandardError = $true + } + $psi.Environment.Add('NO_COLOR', 1) + ($ps = [System.Diagnostics.Process]::Start($psi)).WaitForExit() + $ps.StandardOutput.ReadToEnd() | Should -Not -Contain '\e' + $ps.StandardError.ReadToEnd() | Should -Not -Contain '\e' + } } diff --git a/test/powershell/Host/ScreenReader.Tests.ps1 b/test/powershell/Host/ScreenReader.Tests.ps1 deleted file mode 100644 index 1b11a0e3ffa..00000000000 --- a/test/powershell/Host/ScreenReader.Tests.ps1 +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -Describe "Validate start of console host" -Tag CI { - BeforeAll { - if (-not $IsWindows) { - return - } - - $csharp_source = @' - using System; - using System.Runtime.InteropServices; - - public class ScreenReaderTestUtility { - private const uint SPI_SETSCREENREADER = 0x0047; - - [DllImport("user32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool SystemParametersInfo(uint uiAction, uint uiParam, IntPtr pvParam, uint fWinIni); - - public static bool ActivateScreenReader() { - return SystemParametersInfo(SPI_SETSCREENREADER, 1u, IntPtr.Zero, 0); - } - - public static bool DeactivateScreenReader() { - return SystemParametersInfo(SPI_SETSCREENREADER, 0u, IntPtr.Zero, 0); - } - } -'@ - $utilType = "ScreenReaderTestUtility" -as [type] - if (-not $utilType) { - $utilType = Add-Type -TypeDefinition $csharp_source -PassThru - } - - ## Make the screen reader status active. - $utilType::ActivateScreenReader() - } - - AfterAll { - if ($IsWindows) { - ## Make the screen reader status in-active. - $utilType::DeactivateScreenReader() - } - } - - It "PSReadLine should not be auto-loaded when screen reader status is active" -Skip:(-not $IsWindows) { - if (Test-IsWindowsArm64) { - Set-ItResult -Pending -Because "Needs investigation, PSReadline loads on ARM64" - } - - $output = & "$PSHOME/pwsh" -noprofile -noexit -c "Get-Module PSReadLine; exit" - $output.Length | Should -BeExactly 2 -Because "There should be two lines of output but we got: $output" - - ## The warning message about screen reader should be returned, but the PSReadLine module should not be loaded. - $output[0] | Should -BeLike "Warning:*'Import-Module PSReadLine'." - $output[1] | Should -BeExactly ([string]::Empty) - } -} diff --git a/test/powershell/Host/TabCompletion/BugFix.Tests.ps1 b/test/powershell/Host/TabCompletion/BugFix.Tests.ps1 index 32fd73f8c09..2280d141ac3 100644 --- a/test/powershell/Host/TabCompletion/BugFix.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/BugFix.Tests.ps1 @@ -126,4 +126,173 @@ Describe "Tab completion bug fix" -Tags "CI" { $Runspace.Dispose() } } + + It "Issue#26277 - [CompletionCompleters]::CompleteFilename('') should work" { + $testDir = Join-Path $TestDrive "TempTestDir" + $file1 = Join-Path $testDir "abc.ps1" + $file2 = Join-Path $testDir "def.py" + + New-Item -ItemType Directory -Path $testDir > $null + New-Item -ItemType File -Path $file1 > $null + New-Item -ItemType File -Path $file2 > $null + + try { + Push-Location -Path $testDir + $result = [System.Management.Automation.CompletionCompleters]::CompleteFilename("") + $result | Should -Not -Be $null + $result | Measure-Object | ForEach-Object -MemberName Count | Should -Be 2 + + $item1, $item2 = @($result) + $item1.ListItemText | Should -BeExactly 'abc.ps1' + $item2.ListItemText | Should -BeExactly 'def.py' + } finally { + Pop-Location + } + } + + Context 'Native CLI argument completion' { + BeforeAll { + $testDir = Join-Path $TestDrive "TempTestDir" + $file1 = Join-Path $testDir "abc.ps1" + $file2 = Join-Path $testDir "def.py" + + New-Item -ItemType Directory -Path $testDir > $null + New-Item -ItemType File -Path $file1 > $null + New-Item -ItemType File -Path $file2 > $null + + $dirSep = [System.IO.Path]::DirectorySeparatorChar + $relative_name_abc = ".${dirSep}abc.ps1" + $relative_name_def = ".${dirSep}def.py" + } + + AfterAll { + ## Unregister the completer for 'ping' to avoid affecting other tests. + register-ArgumentCompleter -Native -CommandName ping -ScriptBlock $null + } + + It 'Completer script block returning nothing should fall back to file name completion' { + register-ArgumentCompleter -Native -CommandName ping -ScriptBlock { + param($WordToComplete, $CommandAst, $CursorPosition) + } + + try { + Push-Location -Path $testDir + $cmd = "ping " + $result = TabExpansion2 -inputScript $cmd -cursorColumn $cmd.Length + $result.CompletionMatches | Should -Not -BeNullOrEmpty + $result.CompletionMatches.Count | Should -Be 2 + $result.CompletionMatches[0].CompletionText | Should -BeExactly $relative_name_abc + $result.CompletionMatches[1].CompletionText | Should -BeExactly $relative_name_def + } finally { + Pop-Location + } + } + + It 'Completer script block returning $null should suppress default completion fallback' { + register-ArgumentCompleter -Native -CommandName ping -ScriptBlock { + param($WordToComplete, $CommandAst, $CursorPosition) + return $null + } + + try { + Push-Location -Path $testDir + $cmd = "ping " + ## This call should not throw, and should suppress the default file name completion fallback, returning no results. + $result = TabExpansion2 -inputScript $cmd -cursorColumn $cmd.Length + $result.CompletionMatches.Count | Should -Be 0 + } finally { + Pop-Location + } + } + + It 'Completer script block returning empty string should suppress default completion fallback' { + register-ArgumentCompleter -Native -CommandName ping -ScriptBlock { + param($WordToComplete, $CommandAst, $CursorPosition) + return '' + } + + try { + Push-Location -Path $testDir + $cmd = "ping " + ## This call should not throw, and should suppress the default file name completion fallback, returning no results. + $result = TabExpansion2 -inputScript $cmd -cursorColumn $cmd.Length + $result.CompletionMatches.Count | Should -Be 0 + } finally { + Pop-Location + } + } + + It 'Completer script block returning empty-string-only array should fall back to default completion' { + register-ArgumentCompleter -Native -CommandName ping -ScriptBlock { + param($WordToComplete, $CommandAst, $CursorPosition) + return '', '' + } + + try { + Push-Location -Path $testDir + $cmd = "ping " + ## This call should not throw, and should fall back to the default completion. + $result = TabExpansion2 -inputScript $cmd -cursorColumn $cmd.Length + $result.CompletionMatches.Count | Should -Be 2 + $result.CompletionMatches[0].CompletionText | Should -BeExactly $relative_name_abc + $result.CompletionMatches[1].CompletionText | Should -BeExactly $relative_name_def + } finally { + Pop-Location + } + } + + It 'Completer script block returning null-value-only array should fall back to default completion' { + register-ArgumentCompleter -Native -CommandName ping -ScriptBlock { + param($WordToComplete, $CommandAst, $CursorPosition) + return $null, $null + } + + try { + Push-Location -Path $testDir + $cmd = "ping " + ## This call should not throw, and should fall back to the default completion. + $result = TabExpansion2 -inputScript $cmd -cursorColumn $cmd.Length + $result.CompletionMatches.Count | Should -Be 2 + $result.CompletionMatches[0].CompletionText | Should -BeExactly $relative_name_abc + $result.CompletionMatches[1].CompletionText | Should -BeExactly $relative_name_def + } finally { + Pop-Location + } + } + + It 'Completer script block returning a single string works as expected' { + register-ArgumentCompleter -Native -CommandName ping -ScriptBlock { + param($WordToComplete, $CommandAst, $CursorPosition) + return 'hello' + } + + try { + Push-Location -Path $testDir + $cmd = "ping " + $result = TabExpansion2 -inputScript $cmd -cursorColumn $cmd.Length + $result.CompletionMatches.Count | Should -Be 1 + $result.CompletionMatches[0].CompletionText | Should -BeExactly "hello" + } finally { + Pop-Location + } + } + + It 'Completer script block returning an array that contains non-empty-or-null strings works as expected' { + register-ArgumentCompleter -Native -CommandName ping -ScriptBlock { + param($WordToComplete, $CommandAst, $CursorPosition) + return '', 'hello', $null, 'world' + } + + try { + Push-Location -Path $testDir + $cmd = "ping " + $result = TabExpansion2 -inputScript $cmd -cursorColumn $cmd.Length + $result.CompletionMatches.Count | Should -Be 2 + $result.CompletionMatches[0].CompletionText | Should -BeExactly "hello" + $result.CompletionMatches[1].CompletionText | Should -BeExactly "world" + } finally { + Pop-Location + } + } + } } diff --git a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 index 8e4fdd9d660..f8762a63929 100644 --- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 @@ -1929,8 +1929,12 @@ param([ValidatePattern( Context NativeCommand { BeforeAll { - $nativeCommand = (Get-Command -CommandType Application -TotalCount 1).Name + ## Find a native command that is not 'pwsh'. We will use 'pwsh' for fallback completer tests later. + $nativeCommand = Get-Command -CommandType Application -TotalCount 2 | + Where-Object Name -NotLike pwsh* | + Select-Object -First 1 } + It 'Completes native commands with -' { Register-ArgumentCompleter -Native -CommandName $nativeCommand -ScriptBlock { param($wordToComplete, $ast, $cursorColumn) @@ -1994,6 +1998,52 @@ param([ValidatePattern( $res.CompletionMatches | Should -HaveCount 1 $res.CompletionMatches.CompletionText | Should -BeExactly "-option" } + + It 'Covers an arbitrary unbound native command with -t' { + ## Register a completer for $nativeCommand. + Register-ArgumentCompleter -Native -CommandName $nativeCommand -ScriptBlock { + param($wordToComplete, $ast, $cursorColumn) + if ($wordToComplete -eq '-t') { + return "-terminal" + } + } + + ## Register a fallback native command completer. + Register-ArgumentCompleter -NativeFallback -ScriptBlock { + param($wordToComplete, $ast, $cursorColumn) + if ($wordToComplete -eq '-t') { + return "-testing" + } + } + + ## The specific completer will be used if it exists. + $line = "$nativeCommand -t" + $res = TabExpansion2 -inputScript $line -cursorColumn $line.Length + $res.CompletionMatches | Should -HaveCount 1 + $res.CompletionMatches.CompletionText | Should -BeExactly "-terminal" + + ## Otherwise, the fallback completer will kick in. + $line = "pwsh -t" + $res = TabExpansion2 -inputScript $line -cursorColumn $line.Length + $res.CompletionMatches | Should -HaveCount 1 + $res.CompletionMatches.CompletionText | Should -BeExactly "-testing" + + ## Remove the completer for $nativeCommand. + Register-ArgumentCompleter -Native -CommandName $nativeCommand -ScriptBlock $null + + ## The fallback completer will be used for $nativeCommand. + $line = "$nativeCommand -t" + $res = TabExpansion2 -inputScript $line -cursorColumn $line.Length + $res.CompletionMatches | Should -HaveCount 1 + $res.CompletionMatches.CompletionText | Should -BeExactly "-testing" + + ## Remove the fallback completer for $nativeCommand. + Register-ArgumentCompleter -NativeFallback -ScriptBlock $null + + ## The fallback completer will be used for $nativeCommand. + $res = TabExpansion2 -inputScript $line -cursorColumn $line.Length + $res.CompletionMatches | Should -HaveCount 0 + } } It 'Should complete "Export-Counter -FileFormat" with available output formats' -Pending { @@ -3962,4 +4012,185 @@ Describe "WSMan Config Provider tab complete tests" -Tags Feature,RequireAdminOn # https://github.com/PowerShell/PowerShell/issues/4744 # TODO: move to test cases above once working } + + Context "Tab completion for switch cases on `$PSBoundParameters.Keys" { + It "Should complete parameter names in switch case for `$PSBoundParameters.Keys" { + $inputScript = @" +function Test-Func { + param( + [string]`$Param1, + [string]`$Param2, + [int]`$Count + ) + switch (`$PSBoundParameters.Keys) { + P + } +} +"@ + $cursorPosition = $inputScript.IndexOf("P", $inputScript.IndexOf("Keys)")) + 1 + $res = TabExpansion2 -inputScript $inputScript -cursorColumn $cursorPosition + $res.CompletionMatches | Should -HaveCount 2 + $completionTexts = $res.CompletionMatches.CompletionText | Sort-Object + $completionTexts[0] | Should -BeExactly "Param1" + $completionTexts[1] | Should -BeExactly "Param2" + } + + It "Should complete all parameter names when prefix matches single param" { + $inputScript = @" +function Test-Func { + param( + [string]`$Name, + [int]`$Value + ) + switch (`$PSBoundParameters.Keys) { + N + } +} +"@ + $cursorPosition = $inputScript.IndexOf("N", $inputScript.IndexOf("Keys)")) + 1 + $res = TabExpansion2 -inputScript $inputScript -cursorColumn $cursorPosition + $res.CompletionMatches | Should -HaveCount 1 + $res.CompletionMatches[0].CompletionText | Should -BeExactly "Name" + } + + It "Should complete parameter names in scriptblock param" { + $inputScript = @" +`$sb = { + param( + [string]`$ScriptParam1, + [string]`$ScriptParam2 + ) + switch (`$PSBoundParameters.Keys) { + S + } +} +"@ + $cursorPosition = $inputScript.IndexOf("S", $inputScript.IndexOf("Keys)")) + 1 + $res = TabExpansion2 -inputScript $inputScript -cursorColumn $cursorPosition + $res.CompletionMatches | Should -HaveCount 2 + $completionTexts = $res.CompletionMatches.CompletionText | Sort-Object + $completionTexts[0] | Should -BeExactly "ScriptParam1" + $completionTexts[1] | Should -BeExactly "ScriptParam2" + } + } + + Context "Tab completion for `$PSBoundParameters access patterns" { + It "Should complete parameter names for ContainsKey method" { + $inputScript = @" +function Test-Func { + param([string]`$Param1, [string]`$Param2, [int]`$Count) + if (`$PSBoundParameters.ContainsKey('P')) { } +} +"@ + $cursorPosition = $inputScript.IndexOf("'P'") + 2 + $res = TabExpansion2 -inputScript $inputScript -cursorColumn $cursorPosition + $res.CompletionMatches | Should -HaveCount 2 + $completionTexts = $res.CompletionMatches.CompletionText | Sort-Object + $completionTexts[0] | Should -BeExactly "'Param1'" + $completionTexts[1] | Should -BeExactly "'Param2'" + } + + It "Should complete parameter names for indexer access" { + $inputScript = @" +function Test-Func { + param([string]`$Param1, [string]`$Param2, [int]`$Count) + `$value = `$PSBoundParameters['P'] +} +"@ + $cursorPosition = $inputScript.IndexOf("'P'") + 2 + $res = TabExpansion2 -inputScript $inputScript -cursorColumn $cursorPosition + $res.CompletionMatches | Should -HaveCount 2 + $completionTexts = $res.CompletionMatches.CompletionText | Sort-Object + $completionTexts[0] | Should -BeExactly "'Param1'" + $completionTexts[1] | Should -BeExactly "'Param2'" + } + + It "Should complete parameter names for Remove method" { + $inputScript = @" +function Test-Func { + param([string]`$Param1, [string]`$Param2, [int]`$Count) + `$PSBoundParameters.Remove('C') +} +"@ + $cursorPosition = $inputScript.IndexOf("'C'") + 2 + $res = TabExpansion2 -inputScript $inputScript -cursorColumn $cursorPosition + $res.CompletionMatches | Should -HaveCount 1 + $res.CompletionMatches[0].CompletionText | Should -BeExactly "'Count'" + } + + It "Should complete with double quotes when using double-quoted string" { + $inputScript = @" +function Test-Func { + param([string]`$Param1, [string]`$Param2) + if (`$PSBoundParameters.ContainsKey("P")) { } +} +"@ + $cursorPosition = $inputScript.IndexOf('"P"') + 2 + $res = TabExpansion2 -inputScript $inputScript -cursorColumn $cursorPosition + $res.CompletionMatches | Should -HaveCount 2 + $completionTexts = $res.CompletionMatches.CompletionText | Sort-Object + $completionTexts[0] | Should -BeExactly '"Param1"' + $completionTexts[1] | Should -BeExactly '"Param2"' + } + + It "Should not complete for non-PSBoundParameters variable with indexer" { + $inputScript = @" +function Test-Func { + param([string]`$Param1) + `$hash = @{} + `$value = `$hash['P'] +} +"@ + $cursorPosition = $inputScript.IndexOf("'P'") + 2 + $res = TabExpansion2 -inputScript $inputScript -cursorColumn $cursorPosition + # Should not return Param1 as completion + $paramCompletion = $res.CompletionMatches | Where-Object { $_.CompletionText -eq "'Param1'" } + $paramCompletion | Should -BeNullOrEmpty + } + + It "Should not complete for non-PSBoundParameters variable with ContainsKey" { + $inputScript = @" +function Test-Func { + param([string]`$Param1) + `$hash = @{} + if (`$hash.ContainsKey('P')) { } +} +"@ + $cursorPosition = $inputScript.IndexOf("'P'") + 2 + $res = TabExpansion2 -inputScript $inputScript -cursorColumn $cursorPosition + # Should not return Param1 as completion + $paramCompletion = $res.CompletionMatches | Where-Object { $_.CompletionText -eq "'Param1'" } + $paramCompletion | Should -BeNullOrEmpty + } + + It "Should not complete for non-PSBoundParameters variable with Remove" { + $inputScript = @" +function Test-Func { + param([string]`$Param1) + `$hash = @{} + `$hash.Remove('P') +} +"@ + $cursorPosition = $inputScript.IndexOf("'P'") + 2 + $res = TabExpansion2 -inputScript $inputScript -cursorColumn $cursorPosition + # Should not return Param1 as completion + $paramCompletion = $res.CompletionMatches | Where-Object { $_.CompletionText -eq "'Param1'" } + $paramCompletion | Should -BeNullOrEmpty + } + + It "Should not complete for non-PSBoundParameters variable with double quotes" { + $inputScript = @" +function Test-Func { + param([string]`$Param1) + `$hash = @{} + if (`$hash.ContainsKey("P")) { } +} +"@ + $cursorPosition = $inputScript.IndexOf('"P"') + 2 + $res = TabExpansion2 -inputScript $inputScript -cursorColumn $cursorPosition + # Should not return Param1 as completion + $paramCompletion = $res.CompletionMatches | Where-Object { $_.CompletionText -eq '"Param1"' } + $paramCompletion | Should -BeNullOrEmpty + } + } } diff --git a/test/powershell/Installer/WindowsInstaller.Tests.ps1 b/test/powershell/Installer/WindowsInstaller.Tests.ps1 deleted file mode 100644 index 66bd08e74f5..00000000000 --- a/test/powershell/Installer/WindowsInstaller.Tests.ps1 +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -Describe "Windows Installer" -Tags "Scenario" { - - BeforeAll { - $skipTest = -not $IsWindows - $preRequisitesLink = 'https://aka.ms/pscore6-prereq' - $linkCheckTestCases = @( - @{ Name = "Universal C Runtime"; Url = $preRequisitesLink } - @{ Name = "WMF 4.0"; Url = "https://www.microsoft.com/download/details.aspx?id=40855" } - @{ Name = "WMF 5.0"; Url = "https://www.microsoft.com/download/details.aspx?id=50395" } - @{ Name = "WMF 5.1"; Url = "https://www.microsoft.com/download/details.aspx?id=54616" } - ) - } - - It "WiX (Windows Installer XML) file contains pre-requisites link $preRequisitesLink" -Skip:$skipTest { - $wixProductFile = Join-Path -Path $PSScriptRoot -ChildPath "..\..\..\assets\wix\Product.wxs" - (Get-Content $wixProductFile -Raw).Contains($preRequisitesLink) | Should -BeTrue - } - - ## Running 'Invoke-WebRequest' with WMF download URLs has been failing intermittently, - ## because sometimes the URLs lead to a 'this download is no longer available' page. - ## We use a retry logic here. Retry for 5 times with 1 second interval. - # It "Pre-Requisistes link for '<Name>' is reachable: <url>" -TestCases $linkCheckTestCases -Skip:$skipTest { - It "Pre-Requisistes link for '<Name>' is reachable: <url>" -TestCases $linkCheckTestCases -Pending { - param ($Url) - - foreach ($i in 1..5) { - try { - $result = Invoke-WebRequest $Url -UseBasicParsing - break; - } catch { - Start-Sleep -Seconds 1 - } - } - - $result | Should -Not -Be $null - } -} diff --git a/test/powershell/Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1 b/test/powershell/Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1 index 4e5668071d7..6674697ca2f 100644 --- a/test/powershell/Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1 +++ b/test/powershell/Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1 @@ -833,7 +833,7 @@ class Derived : Base [Derived]::new().foo() '@) - $iss = [System.Management.Automation.Runspaces.initialsessionstate]::CreateDefault2() + $iss = [initialsessionstate]::CreateDefault2() $iss.Commands.Add($ssfe) $ps = [powershell]::Create($iss) @@ -929,7 +929,7 @@ class A : Foo.Bar return [A]::new() '@) - $iss = [System.Management.Automation.Runspaces.initialsessionstate]::CreateDefault() + $iss = [initialsessionstate]::CreateDefault() $iss.Commands.Add($ssfe) $ps = [powershell]::Create($iss) diff --git a/test/powershell/Language/Classes/scripting.Classes.using.tests.ps1 b/test/powershell/Language/Classes/scripting.Classes.using.tests.ps1 index fb3778463da..d4e86e341b1 100644 --- a/test/powershell/Language/Classes/scripting.Classes.using.tests.ps1 +++ b/test/powershell/Language/Classes/scripting.Classes.using.tests.ps1 @@ -417,7 +417,7 @@ function foo() '@ # resolve name to absolute path $scriptToProcessPath = (Get-ChildItem $scriptToProcessPath).FullName - $iss = [System.Management.Automation.Runspaces.initialsessionstate]::CreateDefault() + $iss = [initialsessionstate]::CreateDefault() $iss.StartupScripts.Add($scriptToProcessPath) $ps = [powershell]::Create($iss) diff --git a/test/powershell/Language/Parser/RedirectionOperator.Tests.ps1 b/test/powershell/Language/Parser/RedirectionOperator.Tests.ps1 index d948881f1de..140d92fae88 100644 --- a/test/powershell/Language/Parser/RedirectionOperator.Tests.ps1 +++ b/test/powershell/Language/Parser/RedirectionOperator.Tests.ps1 @@ -127,12 +127,6 @@ Describe "File redirection should have 'DoComplete' called on the underlying pip Describe "Redirection and Set-Variable -append tests" -tags CI { Context "variable redirection should work" { BeforeAll { - if ( $EnabledExperimentalFeatures -contains "PSRedirectToVariable" ) { - $skipTest = $false - } - else { - $skipTest = $true - } $testCases = @{ Name = "Variable should be created"; scriptBlock = { 1..3>variable:a }; Validation = { ($a -join "") | Should -Be ((1..3) -join "") } }, @{ Name = "variable should be appended"; scriptBlock = {1..3>variable:a; 4..6>>variable:a}; Validation = { ($a -join "") | Should -Be ((1..6) -join "")}}, @{ Name = "variable should maintain type"; scriptBlock = {@{one=1}>variable:a};Validation = {$a | Should -BeOfType [hashtable]}}, @@ -220,7 +214,7 @@ Describe "Redirection and Set-Variable -append tests" -tags CI { } - It "<name>" -TestCases $testCases -skip:$skipTest { + It "<name>" -TestCases $testCases { param ( $scriptBlock, $validation ) . $scriptBlock . $validation diff --git a/test/powershell/Language/Scripting/NativeExecution/NativeCommandArguments.Tests.ps1 b/test/powershell/Language/Scripting/NativeExecution/NativeCommandArguments.Tests.ps1 index 8e09df9b699..ead0fb39efb 100644 --- a/test/powershell/Language/Scripting/NativeExecution/NativeCommandArguments.Tests.ps1 +++ b/test/powershell/Language/Scripting/NativeExecution/NativeCommandArguments.Tests.ps1 @@ -5,12 +5,7 @@ param() Describe "Behavior is specific for each platform" -tags "CI" { It "PSNativeCommandArgumentPassing is set to 'Windows' on Windows systems" -skip:(-not $IsWindows) { - if ([Version]::TryParse($PSVersiontable.PSVersion.ToString(), [ref]$null)) { - $PSNativeCommandArgumentPassing | Should -BeExactly "Legacy" - } - else { - $PSNativeCommandArgumentPassing | Should -BeExactly "Windows" - } + $PSNativeCommandArgumentPassing | Should -BeExactly "Windows" } It "PSNativeCommandArgumentPassing is set to 'Standard' on non-Windows systems" -skip:($IsWindows) { $PSNativeCommandArgumentPassing | Should -Be "Standard" diff --git a/test/powershell/Language/Scripting/NativeExecution/NativeCommandPathUpdate.Tests.ps1 b/test/powershell/Language/Scripting/NativeExecution/NativeCommandPathUpdate.Tests.ps1 new file mode 100644 index 00000000000..ff90500ac83 --- /dev/null +++ b/test/powershell/Language/Scripting/NativeExecution/NativeCommandPathUpdate.Tests.ps1 @@ -0,0 +1,373 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +## The "Path Update" feature is only available on Windows. +## Skip the test suite on Unix platforms. +if (-not $IsWindows) { + return; +} + +function GetEnvPathLiteralValue { + param( + [System.EnvironmentVariableTarget] $Target + ) + + if ($Target -eq 'User') { + $regKey = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey('Environment') + } elseif ($Target -eq 'Machine') { + $regKey = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey('SYSTEM\CurrentControlSet\Control\Session Manager\Environment') + } else { + return [PSCustomObject]@{ Kind = $null; Value = $env:Path } + } + + try { + $kind = $regKey.GetValueKind('Path') + $value = $regKey.GetValue('Path', $null, [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames) + + return [PSCustomObject]@{ + Kind = $kind + Value = $value + } + } + finally { + ${regKey}?.Dispose() + } +} + +function RestoreEnvPath { + param( + [System.EnvironmentVariableTarget] $Target, + [Microsoft.Win32.RegistryValueKind] $ValueKind, + [string] $LiteralValue + ) + + ## Open the registry key with 'write' access. + if ($Target -eq 'User') { + $regKey = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey('Environment', $true) + } elseif ($Target -eq 'Machine') { + $regKey = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey('SYSTEM\CurrentControlSet\Control\Session Manager\Environment', $true) + } else { + ## Ignore value kind when restoring the in-proc env Path. + $env:Path = $LiteralValue + return + } + + try { + $regKey.SetValue('Path', $LiteralValue, $ValueKind) + } finally { + ${regKey}?.Dispose() + } +} + +function UpdatePackageManager { + param( + [Parameter(ParameterSetName = 'Add')] + [switch] $Add, + + [Parameter(ParameterSetName = 'Remove')] + [switch] $Remove, + + [string] $Name + ) + + $regKeyPath = 'HKLM:\Software\Microsoft\Command Processor\KnownPackageManagers' + $keyExists = Test-Path -Path $regKeyPath + if (-not $keyExists) { + Write-Host -ForegroundColor Cyan "The registry key 'KnownPackageManagers' doesn't exist." + } + + $subKeyPath = "$regKeyPath\$Name" + if ($Add) { + $null = New-Item $subKeyPath -Force -ErrorAction Stop + } + elseif ($Remove -and $keyExists) { + Remove-Item $subKeyPath -Recurse -Force -ErrorAction Stop + } +} + +Describe "Path update for package managers" -tags @('CI', 'RequireAdminOnWindows') { + + It "Path update is off for an executable that is not registered" { + try { + $oldUserPath = GetEnvPathLiteralValue -Target 'User' + $oldSysPath = GetEnvPathLiteralValue -Target 'Machine' + $oldProcPath = $env:Path + + testexe -updateuserandsystempath + + $newUserPath = GetEnvPathLiteralValue -Target 'User' + $newUserPath.Kind | Should -Be $oldUserPath.Kind -Because "Value kind should not be changed" + $newUserPath.Value | Should -BeLike "$($oldUserPath.Value)*X:\not-exist-user-path" -Because "'testexe -updateuserandsystempath' should append 'X:\not-exist-user-path' to the User Path." + + $newSysPath = GetEnvPathLiteralValue -Target 'Machine' + $newSysPath.Kind | Should -Be $oldSysPath.Kind -Because "Value kind should not be changed" + $newSysPath.Value | Should -BeLike "X:\not-exist-sys-path*$($oldSysPath.Value)" -Because "'testexe -updateuserandsystempath' should prepend 'X:\not-exist-sys-path' to the System Path." + + $newProcPath = $env:Path + $newProcPath | Should -Be $oldProcPath -Because "'testexe -updateuserpath' doesn't change the Process Path and the executable 'testexe' is not in the package manager list." + } + finally { + if ($oldUserPath -ne $null) { + RestoreEnvPath -Target 'User' -ValueKind $oldUserPath.Kind -LiteralValue $oldUserPath.Value + } + + if ($oldSysPath -ne $null) { + RestoreEnvPath -Target 'Machine' -ValueKind $oldSysPath.Kind -LiteralValue $oldSysPath.Value + } + } + } + + ## Add the executable name without extension to the list of package managers. + Context "Add 'testexe' to the list and test 'Path Update'" { + BeforeAll { + UpdatePackageManager -Add -Name 'testexe' + } + + AfterAll { + UpdatePackageManager -Remove -Name 'testexe' + } + + It "Test when only User Path is changed" { + try { + $oldUserPath = GetEnvPathLiteralValue -Target 'User' + + $oldProcPath, $newProcPath = pwsh -noprofile -c { + $oldPath = $env:Path + + ## New item 'X:\not-exist-user-path' will be appended to User Path. + testexe -updateuserpath + + $newPath = $env:Path + $oldPath, $newPath + } + + $newProcPath.Length | Should -BeGreaterThan $oldProcPath.Length -Because "Path should be updated. The new path item added to 'User Path' should be appended to 'Process Path'." + $newProcPath.IndexOf($oldProcPath) | Should -Be 0 -Because "Path should be updated. The new path item added to 'User Path' should be appended to 'Process Path'." + + $newItem = $newProcPath.SubString($oldProcPath.Length) + if ($oldProcPath.EndsWith(';')) { + $newItem | Should -Be 'X:\not-exist-user-path' + } + else { + $newItem | Should -Be ';X:\not-exist-user-path' + } + } + finally { + if ($oldUserPath -ne $null) { + RestoreEnvPath -Target 'User' -ValueKind $oldUserPath.Kind -LiteralValue $oldUserPath.Value + } + } + } + + It "Test when only System Path is changed" { + try { + $oldSysPath = GetEnvPathLiteralValue -Target 'Machine' + + $oldProcPath, $newProcPath = pwsh -noprofile -c { + $oldPath = $env:Path + + ## New item 'X:\not-exist-sys-path' will be prepended to System Path. + testexe -updatesystempath > $null + + $newPath = $env:Path + $oldPath, $newPath + } + + $newProcPath.Length | Should -BeGreaterThan $oldProcPath.Length -Because "Path should be updated. The new path item added to 'System Path' should be appended to 'Process Path'." + $newProcPath.IndexOf($oldProcPath) | Should -Be 0 -Because "Path should be updated. The new path item added to 'System Path' should be appended to 'Process Path'." + + $newItem = $newProcPath.SubString($oldProcPath.Length) + if ($oldProcPath.EndsWith(';')) { + $newItem | Should -Be 'X:\not-exist-sys-path' + } + else { + $newItem | Should -Be ';X:\not-exist-sys-path' + } + } + finally { + if ($oldSysPath -ne $null) { + RestoreEnvPath -Target 'Machine' -ValueKind $oldSysPath.Kind -LiteralValue $oldSysPath.Value + } + } + } + + It "Test when both User and System Paths are changed" { + try { + $oldUserPath = GetEnvPathLiteralValue -Target 'User' + $oldSysPath = GetEnvPathLiteralValue -Target 'Machine' + + $oldProcPath, $newProcPath = pwsh -noprofile -c { + $oldPath = $env:Path + + ## New item 'X:\not-exist-user-path' will be appended to User Path. + ## New item 'X:\not-exist-sys-path' will be prepended to System Path. + $null = testexe -updateuserandsystempath + + $newPath = $env:Path + $oldPath, $newPath + } + + $newProcPath.Length | Should -BeGreaterThan $oldProcPath.Length -Because "Path should be updated. The new path items should be appended to 'Process Path'." + $newProcPath.IndexOf($oldProcPath) | Should -Be 0 -Because "Path should be updated. The new path items should be appended to 'Process Path'." + + $newItem = $newProcPath.SubString($oldProcPath.Length) + if ($oldProcPath.EndsWith(';')) { + $newItem | Should -Be 'X:\not-exist-user-path;X:\not-exist-sys-path' + } + else { + $newItem | Should -Be ';X:\not-exist-user-path;X:\not-exist-sys-path' + } + } + finally { + if ($oldUserPath -ne $null) { + RestoreEnvPath -Target 'User' -ValueKind $oldUserPath.Kind -LiteralValue $oldUserPath.Value + } + + if ($oldSysPath -ne $null) { + RestoreEnvPath -Target 'Machine' -ValueKind $oldSysPath.Kind -LiteralValue $oldSysPath.Value + } + } + } + + It "Test when neither User nor System Path is changed" { + $oldProcPath, $newProcPath = pwsh -noprofile -c { + $oldPath = $env:Path + + ## Print help message and exit. + testexe -h > $null + + $newPath = $env:Path + $oldPath, $newPath + } + + $newProcPath | Should -Be $oldProcPath -Because "'testexe -h' doesn't change the env Path." + } + } + + ## Add the executable name with extension to the list of package managers. + Context "Add 'testexe.exe' to the list and test 'Path Update'" { + BeforeAll { + UpdatePackageManager -Add -Name 'testexe.exe' + } + + AfterAll { + UpdatePackageManager -Remove -Name 'testexe.exe' + } + + It "Test when only User Path is changed" { + try { + $oldUserPath = GetEnvPathLiteralValue -Target 'User' + + $oldProcPath, $newProcPath = pwsh -noprofile -c { + $oldPath = $env:Path + + ## New item 'X:\not-exist-user-path' will be appended to User Path. + testexe -updateuserpath + + $newPath = $env:Path + $oldPath, $newPath + } + + $newProcPath.Length | Should -BeGreaterThan $oldProcPath.Length -Because "Path should be updated. The new path item added to 'User Path' should be appended to 'Process Path'." + $newProcPath.IndexOf($oldProcPath) | Should -Be 0 -Because "Path should be updated. The new path item added to 'User Path' should be appended to 'Process Path'." + + $newItem = $newProcPath.SubString($oldProcPath.Length) + if ($oldProcPath.EndsWith(';')) { + $newItem | Should -Be 'X:\not-exist-user-path' + } + else { + $newItem | Should -Be ';X:\not-exist-user-path' + } + } + finally { + if ($oldUserPath -ne $null) { + RestoreEnvPath -Target 'User' -ValueKind $oldUserPath.Kind -LiteralValue $oldUserPath.Value + } + } + } + + It "Test when only System Path is changed" { + try { + $oldSysPath = GetEnvPathLiteralValue -Target 'Machine' + + $oldProcPath, $newProcPath = pwsh -noprofile -c { + $oldPath = $env:Path + + ## New item 'X:\not-exist-sys-path' will be prepended to System Path. + testexe -updatesystempath > $null + + $newPath = $env:Path + $oldPath, $newPath + } + + $newProcPath.Length | Should -BeGreaterThan $oldProcPath.Length -Because "Path should be updated. The new path item added to 'System Path' should be appended to 'Process Path'." + $newProcPath.IndexOf($oldProcPath) | Should -Be 0 -Because "Path should be updated. The new path item added to 'System Path' should be appended to 'Process Path'." + + $newItem = $newProcPath.SubString($oldProcPath.Length) + if ($oldProcPath.EndsWith(';')) { + $newItem | Should -Be 'X:\not-exist-sys-path' + } + else { + $newItem | Should -Be ';X:\not-exist-sys-path' + } + } + finally { + if ($oldSysPath -ne $null) { + RestoreEnvPath -Target 'Machine' -ValueKind $oldSysPath.Kind -LiteralValue $oldSysPath.Value + } + } + } + + It "Test when both User and System Paths are changed" { + try { + $oldUserPath = GetEnvPathLiteralValue -Target 'User' + $oldSysPath = GetEnvPathLiteralValue -Target 'Machine' + + $oldProcPath, $newProcPath = pwsh -noprofile -c { + $oldPath = $env:Path + + ## New item 'X:\not-exist-user-path' will be appended to User Path. + ## New item 'X:\not-exist-sys-path' will be prepended to System Path. + $null = testexe -updateuserandsystempath + + $newPath = $env:Path + $oldPath, $newPath + } + + $newProcPath.Length | Should -BeGreaterThan $oldProcPath.Length -Because "Path should be updated. The new path items should be appended to 'Process Path'." + $newProcPath.IndexOf($oldProcPath) | Should -Be 0 -Because "Path should be updated. The new path items should be appended to 'Process Path'." + + $newItem = $newProcPath.SubString($oldProcPath.Length) + if ($oldProcPath.EndsWith(';')) { + $newItem | Should -Be 'X:\not-exist-user-path;X:\not-exist-sys-path' + } + else { + $newItem | Should -Be ';X:\not-exist-user-path;X:\not-exist-sys-path' + } + } + finally { + if ($oldUserPath -ne $null) { + RestoreEnvPath -Target 'User' -ValueKind $oldUserPath.Kind -LiteralValue $oldUserPath.Value + } + + if ($oldSysPath -ne $null) { + RestoreEnvPath -Target 'Machine' -ValueKind $oldSysPath.Kind -LiteralValue $oldSysPath.Value + } + } + } + + It "Test when neither User nor System Path is changed" { + $oldProcPath, $newProcPath = pwsh -noprofile -c { + $oldPath = $env:Path + + ## Print help message and exit. + testexe -h > $null + + $newPath = $env:Path + $oldPath, $newPath + } + + $newProcPath | Should -Be $oldProcPath -Because "'testexe -h' doesn't change the env Path." + } + } +} diff --git a/test/powershell/Language/Scripting/NativeExecution/NativeWindowsTildeExpansion.Tests.ps1 b/test/powershell/Language/Scripting/NativeExecution/NativeWindowsTildeExpansion.Tests.ps1 index 04156099fa4..3a478607c08 100644 --- a/test/powershell/Language/Scripting/NativeExecution/NativeWindowsTildeExpansion.Tests.ps1 +++ b/test/powershell/Language/Scripting/NativeExecution/NativeWindowsTildeExpansion.Tests.ps1 @@ -5,7 +5,6 @@ Describe 'Native Windows tilde expansion tests' -tags "CI" { BeforeAll { $originalDefaultParams = $PSDefaultParameterValues.Clone() $PSDefaultParameterValues["it:skip"] = -Not $IsWindows - $EnabledExperimentalFeatures.Contains('PSNativeWindowsTildeExpansion') | Should -BeTrue } AfterAll { @@ -21,12 +20,13 @@ Describe 'Native Windows tilde expansion tests' -tags "CI" { cmd /c echo ~/foo | Should -BeExactly "$($ExecutionContext.SessionState.Provider.Get("FileSystem").Home)/foo" cmd /c echo ~\foo | Should -BeExactly "$($ExecutionContext.SessionState.Provider.Get("FileSystem").Home)\foo" } - It '~ should not be replaced when quoted' { - cmd /c echo '~' | Should -BeExactly '~' - cmd /c echo "~" | Should -BeExactly '~' - cmd /c echo '~/foo' | Should -BeExactly '~/foo' - cmd /c echo "~/foo" | Should -BeExactly '~/foo' + + It '~ should not be replaced when quoted' { + cmd /c echo '~' | Should -BeExactly '~' + cmd /c echo "~" | Should -BeExactly '~' + cmd /c echo '~/foo' | Should -BeExactly '~/foo' + cmd /c echo "~/foo" | Should -BeExactly '~/foo' cmd /c echo '~\foo' | Should -BeExactly '~\foo' - cmd /c echo "~\foo" | Should -BeExactly '~\foo' - } + cmd /c echo "~\foo" | Should -BeExactly '~\foo' + } } diff --git a/test/powershell/Language/Scripting/Requires.Tests.ps1 b/test/powershell/Language/Scripting/Requires.Tests.ps1 index d4cad910e10..b5cbb397325 100644 --- a/test/powershell/Language/Scripting/Requires.Tests.ps1 +++ b/test/powershell/Language/Scripting/Requires.Tests.ps1 @@ -41,7 +41,7 @@ Describe "Requires tests" -Tags "CI" { BeforeAll { $currentVersion = $PSVersionTable.PSVersion - $powerShellVersions = "1.0", "2.0", "3.0", "4.0", "5.0", "5.1", "6.0", "6.1", "6.2", "7.0", "7.1", "7.2", "7.3", "7.4", "7.5", "7.6" + $powerShellVersions = "1.0", "2.0", "3.0", "4.0", "5.0", "5.1", "6.0", "6.1", "6.2", "7.0", "7.1", "7.2", "7.3", "7.4", "7.5", "7.6", "7.7" $latestVersion = [version]($powerShellVersions | Sort-Object -Descending -Top 1) $nonExistingMinor = "$($latestVersion.Major).$($latestVersion.Minor + 1)" $nonExistingMajor = "$($latestVersion.Major + 1).0" diff --git a/test/powershell/Language/Scripting/ScriptHelp.NotesFormatting.Tests.ps1 b/test/powershell/Language/Scripting/ScriptHelp.NotesFormatting.Tests.ps1 new file mode 100644 index 00000000000..77c0c65b1af --- /dev/null +++ b/test/powershell/Language/Scripting/ScriptHelp.NotesFormatting.Tests.ps1 @@ -0,0 +1,27 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe 'Comment-based help NOTES section formatting' -Tags "CI", "Feature" { + It 'NOTES section should have single blank line after header and 4-space indentation' { + $helpText = Get-Help Remove-PSSession -Full | Out-String + $lines = $helpText -split "`r?`n" + + # Find NOTES section + $notesIndex = -1 + for ($i = 0; $i -lt $lines.Count; $i++) { + if ($lines[$i] -match '^NOTES\s*$') { + $notesIndex = $i + break + } + } + + $notesIndex | Should -BeGreaterThan -1 -Because "NOTES section should exist" + + # The line after NOTES header should be blank + $lines[$notesIndex + 1] | Should -Match '^\s*$' -Because "There should be a blank line after NOTES header" + + # The second line after NOTES header should have content with 4-space indentation + $contentLine = $lines[$notesIndex + 2] + $contentLine | Should -Match '^[ ]{4}\S' -Because "Content should have exactly 4-space indentation" + } +} diff --git a/test/powershell/Modules/Microsoft.PowerShell.Core/Import-Module.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Core/Import-Module.Tests.ps1 index 6885abe847d..19dc80caf28 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Core/Import-Module.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Core/Import-Module.Tests.ps1 @@ -62,6 +62,7 @@ Describe "Import-Module" -Tags "CI" { 'X86' { 'x86' } 'X64' { 'amd64' } 'Arm64' { 'arm' } + 'Arm' { 'arm' } default { throw "Unknown processor architecture" } } New-ModuleManifest -Path "$TestDrive\TestModule.psd1" -ProcessorArchitecture $currentProcessorArchitecture diff --git a/test/powershell/Modules/Microsoft.PowerShell.Core/Where-Object.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Core/Where-Object.Tests.ps1 index c12a1bdb516..eae7d9951a3 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Core/Where-Object.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Core/Where-Object.Tests.ps1 @@ -142,4 +142,242 @@ Describe "Where-Object" -Tags "CI" { $Result[0] | Should -Be $dynObj $Result[1] | Should -Be $dynObj } + + Context "Switch parameter explicit `$false handling" { + BeforeAll { + $testData = @( + [PSCustomObject]@{ Name = 'Alice'; Age = 25; City = 'New York' } + [PSCustomObject]@{ Name = 'Bob'; Age = 30; City = 'Boston' } + [PSCustomObject]@{ Name = 'Charlie'; Age = 35; City = 'Chicago' } + ) + } + + It "-EQ:`$false should behave like default boolean evaluation" { + # With -EQ:$false, the parameter is not specified, so default boolean evaluation applies + # "... | Where-Object Name" is equivalent to "... | Where-Object {$true -eq $_.Name}" + # Non-empty strings are truthy + $result = $testData | Where-Object Name -EQ:$false + $result | Should -HaveCount 3 + } + + It "-GT:`$false should behave like default boolean evaluation" { + # With -GT:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Age -GT:$false + $result | Should -HaveCount 3 + } + + It "-LT:`$false should behave like default boolean evaluation" { + # With -LT:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Age -LT:$false + $result | Should -HaveCount 3 + } + + It "-NE:`$false should behave like default boolean evaluation" { + # With -NE:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Name -NE:$false + $result | Should -HaveCount 3 + } + + It "-GE:`$false should behave like default boolean evaluation" { + # With -GE:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Age -GE:$false + $result | Should -HaveCount 3 + } + + It "-LE:`$false should behave like default boolean evaluation" { + # With -LE:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Age -LE:$false + $result | Should -HaveCount 3 + } + + It "-NotLike:`$false should behave like default boolean evaluation" { + # With -NotLike:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object City -NotLike:$false + $result | Should -HaveCount 3 + } + + It "-NotMatch:`$false should behave like default boolean evaluation" { + # With -NotMatch:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Name -NotMatch:$false + $result | Should -HaveCount 3 + } + + It "-NotContains:`$false should behave like default boolean evaluation" { + # With -NotContains:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Name -NotContains:$false + $result | Should -HaveCount 3 + } + + It "-NotIn:`$false should behave like default boolean evaluation" { + # With -NotIn:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Name -NotIn:$false + $result | Should -HaveCount 3 + } + + It "-IsNot:`$false should behave like default boolean evaluation" { + # With -IsNot:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Name -IsNot:$false + $result | Should -HaveCount 3 + } + + It "-Like:`$false should behave like default boolean evaluation" { + # With -Like:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object City -Like:$false + $result | Should -HaveCount 3 + } + + It "-Match:`$false should behave like default boolean evaluation" { + # With -Match:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Name -Match:$false + $result | Should -HaveCount 3 + } + + It "-Contains:`$false should behave like default boolean evaluation" { + # With -Contains:$false, the parameter is not specified, so default boolean evaluation applies + # "... | Where-Object Name" checks if Name property is truthy + $result = $testData | Where-Object Name -Contains:$false + $result | Should -HaveCount 3 + } + + It "-In:`$false should behave like default boolean evaluation" { + # With -In:$false, the parameter is not specified, so default boolean evaluation applies + # "... | Where-Object Name" checks if Name property is truthy + $result = $testData | Where-Object Name -In:$false + $result | Should -HaveCount 3 + } + + It "-Is:`$false should behave like default boolean evaluation" { + # With -Is:$false, the parameter is not specified, so default boolean evaluation applies + # "... | Where-Object Name" checks if Name property is truthy + $result = $testData | Where-Object Name -Is:$false + $result | Should -HaveCount 3 + } + + It "-Not:`$false should behave like default boolean evaluation" { + $testData2 = @( + [PSCustomObject]@{ Name = ''; Value = 1 } + [PSCustomObject]@{ Name = 'Test'; Value = 2 } + ) + # With -Not:$false, the parameter is not specified, so default boolean evaluation applies + # "... | Where-Object Name" returns objects where Name is truthy (non-empty) + $result = $testData2 | Where-Object Name -Not:$false + $result | Should -HaveCount 1 + $result.Name | Should -Be 'Test' + } + + It "-GT:`$false with -Value should throw an error requiring an operator" { + # When a switch parameter is set to $false with -Value specified, + # it should throw an error because no valid operator is active + { $testData | Where-Object Age -GT:$false -Value 25 } | Should -Throw -ErrorId 'OperatorNotSpecified,Microsoft.PowerShell.Commands.WhereObjectCommand' + } + + It "-EQ:`$false with -Value should throw an error requiring an operator" { + # Similar behavior for -EQ:$false with -Value + { $testData | Where-Object Name -EQ:$false -Value 'Alice' } | Should -Throw -ErrorId 'OperatorNotSpecified,Microsoft.PowerShell.Commands.WhereObjectCommand' + } + + It "-Like:`$false with -Value should throw an error requiring an operator" { + # Similar behavior for -Like:$false with -Value + { $testData | Where-Object Name -Like:$false -Value 'A*' } | Should -Throw -ErrorId 'OperatorNotSpecified,Microsoft.PowerShell.Commands.WhereObjectCommand' + } + + It "-CEQ:`$false should behave like default boolean evaluation" { + # With -CEQ:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Name -CEQ:$false + $result | Should -HaveCount 3 + } + + It "-CNE:`$false should behave like default boolean evaluation" { + # With -CNE:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Name -CNE:$false + $result | Should -HaveCount 3 + } + + It "-CGT:`$false should behave like default boolean evaluation" { + # With -CGT:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Age -CGT:$false + $result | Should -HaveCount 3 + } + + It "-CLT:`$false should behave like default boolean evaluation" { + # With -CLT:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Age -CLT:$false + $result | Should -HaveCount 3 + } + + It "-CGE:`$false should behave like default boolean evaluation" { + # With -CGE:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Age -CGE:$false + $result | Should -HaveCount 3 + } + + It "-CLE:`$false should behave like default boolean evaluation" { + # With -CLE:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Age -CLE:$false + $result | Should -HaveCount 3 + } + + It "-CLike:`$false should behave like default boolean evaluation" { + # With -CLike:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object City -CLike:$false + $result | Should -HaveCount 3 + } + + It "-CNotLike:`$false should behave like default boolean evaluation" { + # With -CNotLike:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object City -CNotLike:$false + $result | Should -HaveCount 3 + } + + It "-CMatch:`$false should behave like default boolean evaluation" { + # With -CMatch:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Name -CMatch:$false + $result | Should -HaveCount 3 + } + + It "-CNotMatch:`$false should behave like default boolean evaluation" { + # With -CNotMatch:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Name -CNotMatch:$false + $result | Should -HaveCount 3 + } + + It "-CContains:`$false should behave like default boolean evaluation" { + # With -CContains:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Name -CContains:$false + $result | Should -HaveCount 3 + } + + It "-CNotContains:`$false should behave like default boolean evaluation" { + # With -CNotContains:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Name -CNotContains:$false + $result | Should -HaveCount 3 + } + + It "-CIn:`$false should behave like default boolean evaluation" { + # With -CIn:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Name -CIn:$false + $result | Should -HaveCount 3 + } + + It "-CNotIn:`$false should behave like default boolean evaluation" { + # With -CNotIn:$false, the parameter is not specified, so default boolean evaluation applies + $result = $testData | Where-Object Name -CNotIn:$false + $result | Should -HaveCount 3 + } + + It "-CEQ:`$false with -Value should throw an error requiring an operator" { + # Similar behavior for -CEQ:$false with -Value + { $testData | Where-Object Name -CEQ:$false -Value 'Alice' } | Should -Throw -ErrorId 'OperatorNotSpecified,Microsoft.PowerShell.Commands.WhereObjectCommand' + } + + It "-CGT:`$false with -Value should throw an error requiring an operator" { + # Similar behavior for -CGT:$false with -Value + { $testData | Where-Object Age -CGT:$false -Value 25 } | Should -Throw -ErrorId 'OperatorNotSpecified,Microsoft.PowerShell.Commands.WhereObjectCommand' + } + + It "-CLike:`$false with -Value should throw an error requiring an operator" { + # Similar behavior for -CLike:$false with -Value + { $testData | Where-Object Name -CLike:$false -Value 'A*' } | Should -Throw -ErrorId 'OperatorNotSpecified,Microsoft.PowerShell.Commands.WhereObjectCommand' + } + } } diff --git a/test/powershell/Modules/Microsoft.PowerShell.LocalAccounts/Pester.Command.Cmdlets.LocalAccounts.LocalUser.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.LocalAccounts/Pester.Command.Cmdlets.LocalAccounts.LocalUser.Tests.ps1 index e6e5ebb8d4f..2e56df87256 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.LocalAccounts/Pester.Command.Cmdlets.LocalAccounts.LocalUser.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.LocalAccounts/Pester.Command.Cmdlets.LocalAccounts.LocalUser.Tests.ps1 @@ -4,6 +4,9 @@ # Module removed due to #4272 # disabling tests +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] +param() + return Set-Variable dateInFuture -Option Constant -Value "12/12/2036 09:00" @@ -1557,4 +1560,3 @@ try { finally { $global:PSDefaultParameterValues = $originalDefaultParameterValues } - diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Clipboard.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Clipboard.Tests.ps1 index 7cd4f492bfa..62f8d77a44c 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Clipboard.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Clipboard.Tests.ps1 @@ -36,6 +36,13 @@ Describe 'Clipboard cmdlet tests' -Tag CI { Get-Clipboard -Raw | Should -BeExactly "1$([Environment]::NewLine)2" } + It 'Get-Clipboard -Delimiter should return items based on the delimiter' { + Set-Clipboard -Value "Line1`r`nLine2`nLine3" + $result = Get-Clipboard -Delimiter "`r`n", "`n" + $result.Count | Should -Be 3 + $result | ForEach-Object -Process {$_.Length | Should -Be "LineX".Length} + } + It 'Set-Clipboard -Append will add text' { 'hello' | Set-Clipboard 'world' | Set-Clipboard -Append diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Join-Path.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Join-Path.Tests.ps1 index a66bddd5e4f..9b0898fee90 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Join-Path.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Join-Path.Tests.ps1 @@ -84,4 +84,102 @@ Describe "Join-Path cmdlet tests" -Tags "CI" { $result = Join-Path -Path $Path -ChildPath $ChildPath $result | Should -BeExactly $ExpectedResult } + It "should handle extension parameter: <TestName>" -TestCases @( + @{ + TestName = "change extension" + ChildPath = "file.txt" + Extension = ".log" + ExpectedChildPath = "file.log" + } + @{ + TestName = "add extension to file without extension" + ChildPath = "file" + Extension = ".txt" + ExpectedChildPath = "file.txt" + } + @{ + TestName = "extension without leading dot" + ChildPath = "file.txt" + Extension = "log" + ExpectedChildPath = "file.log" + } + @{ + TestName = "double extension with dot" + ChildPath = "file.txt" + Extension = ".tar.gz" + ExpectedChildPath = "file.tar.gz" + } + @{ + TestName = "double extension without dot" + ChildPath = "file.txt" + Extension = "tar.gz" + ExpectedChildPath = "file.tar.gz" + } + @{ + TestName = "remove extension with empty string" + ChildPath = "file.txt" + Extension = "" + ExpectedChildPath = "file" + } + @{ + TestName = "preserve dots in base name when removing extension with empty string" + ChildPath = "file...txt" + Extension = "" + ExpectedChildPath = "file.." + } + @{ + TestName = "replace only the last extension for files with multiple dots" + ChildPath = "file.backup.txt" + Extension = ".log" + ExpectedChildPath = "file.backup.log" + } + @{ + TestName = "preserve dots in base name when changing extension" + ChildPath = "file...txt" + Extension = ".md" + ExpectedChildPath = "file...md" + } + @{ + TestName = "add extension to directory-like path" + ChildPath = "subfolder" + Extension = ".log" + ExpectedChildPath = "subfolder.log" + } + ) { + param($TestName, $ChildPath, $Extension, $ExpectedChildPath) + $result = Join-Path -Path "folder" -ChildPath $ChildPath -Extension $Extension + $result | Should -BeExactly "folder${SepChar}${ExpectedChildPath}" + } + It "should handle extension parameter with multiple child path segments: <TestName>" -TestCases @( + @{ + TestName = "change extension when joining multiple child path segments" + ChildPaths = @("subfolder", "file.txt") + Extension = ".log" + ExpectedPath = "folder${SepChar}subfolder${SepChar}file.log" + } + ) { + param($TestName, $ChildPaths, $Extension, $ExpectedPath) + $result = Join-Path -Path "folder" -ChildPath $ChildPaths -Extension $Extension + $result | Should -BeExactly $ExpectedPath + } + It "should change extension for multiple paths" { + $result = Join-Path -Path "folder1", "folder2" -ChildPath "file.txt" -Extension ".log" + $result.Count | Should -Be 2 + $result[0] | Should -BeExactly "folder1${SepChar}file.log" + $result[1] | Should -BeExactly "folder2${SepChar}file.log" + } + It "should resolve path when -Extension changes to existing file" { + New-Item -Path TestDrive:\testfile.log -ItemType File -Force | Out-Null + $result = Join-Path -Path TestDrive: -ChildPath "testfile.txt" -Extension ".log" -Resolve + $result | Should -BeLike "*testfile.log" + } + It "should throw error when -Extension changes to non-existing file with -Resolve" { + { Join-Path -Path TestDrive: -ChildPath "testfile.txt" -Extension ".nonexistent" -Resolve -ErrorAction Stop; Throw "Previous statement unexpectedly succeeded..." } | + Should -Throw -ErrorId "PathNotFound,Microsoft.PowerShell.Commands.JoinPathCommand" + } + It "should accept Extension from pipeline by property name" { + $obj = [PSCustomObject]@{ Path = "folder"; ChildPath = "file.txt"; Extension = ".log" } + $result = $obj | Join-Path + $result | Should -BeExactly "folder${SepChar}file.log" + } } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Rename-Computer.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Rename-Computer.Tests.ps1 index 82f944612f9..f1937f824a6 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Rename-Computer.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Rename-Computer.Tests.ps1 @@ -7,6 +7,7 @@ $DefaultResultValue = 0 try { # set up for testing + $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() $PSDefaultParameterValues["it:skip"] = ! $IsWindows Enable-Testhook -testhookName $RenameTesthook # we also set TestStopComputer @@ -73,7 +74,7 @@ try } finally { - $PSDefaultParameterValues.Remove("it:skip") + $global:PSDefaultParameterValues = $originalDefaultParameterValues Disable-Testhook -testhookName $RenameTestHook Disable-Testhook -testhookName TestStopComputer Set-TesthookResult -testhookName $RenameResultName -value 0 diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 index 2c5f0fed3ee..bcb7a56ccde 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Set-Service.Tests.ps1 @@ -1,5 +1,9 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. + +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] +param() + Import-Module (Join-Path -Path $PSScriptRoot '..\Microsoft.PowerShell.Security\certificateCommon.psm1') Describe "Set/New/Remove-Service cmdlet tests" -Tags "Feature", "RequireAdminOnWindows" { diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Split-Path.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Split-Path.Tests.ps1 index b9537a4a961..e34126b101f 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Split-Path.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Split-Path.Tests.ps1 @@ -96,7 +96,33 @@ Describe "Split-Path" -Tags "CI" { Split-Path -Parent "\\server1\share1" | Should -BeExactly "${dirSep}${dirSep}server1" } - It 'Does not split a drive leter'{ - Split-Path -Path 'C:\' | Should -BeNullOrEmpty + It 'Does not split a drive letter' { + Split-Path -Path 'C:\' | Should -BeNullOrEmpty + } + + It "Should handle explicit -Qualifier:`$false parameter value correctly" { + # When -Qualifier:$false is specified, it should behave like -Parent (default) + # For env:PATH, the parent is empty string + $result = Split-Path -Path "env:PATH" -Qualifier:$false + $result | Should -BeNullOrEmpty + } + + It "Should handle explicit -NoQualifier:`$false parameter value correctly" { + # When -NoQualifier:$false is specified, it should behave like -Parent (default) + # For env:PATH with no qualifier, we expect empty string (parent of PATH in env drive) + $result = Split-Path -Path "env:PATH" -NoQualifier:$false + $result | Should -BeNullOrEmpty + } + + It "Should handle explicit -Leaf:`$false parameter value correctly" { + # When -Leaf:$false is specified, it should behave like -Parent (default) + $dirSep = [string]([System.IO.Path]::DirectorySeparatorChar) + Split-Path -Path "/usr/bin" -Leaf:$false | Should -BeExactly "${dirSep}usr" + } + + It "Should handle explicit -IsAbsolute:`$false parameter value correctly" { + # When -IsAbsolute:$false is specified, it should behave like -Parent (default) + $dirSep = [string]([System.IO.Path]::DirectorySeparatorChar) + Split-Path -Path "fs:/usr/bin" -IsAbsolute:$false | Should -BeExactly "fs:${dirSep}usr" } } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Test-Connection.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Test-Connection.Tests.ps1 index 2138a447581..ad01b461b55 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Test-Connection.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Test-Connection.Tests.ps1 @@ -258,6 +258,15 @@ Describe "Test-Connection" -tags "CI", "RequireSudoOnUnix" { $pingResults.Where( { $_.Status -eq 'Success' }, 'Default', 1 ).BufferSize | Should -Be 32 } } + + It "Repeat with explicit false should behave like default ping" { + # -Repeat:$false should behave the same as not specifying -Repeat + $pingResults = Test-Connection $targetAddress -Repeat:$false + + # Should get exactly 4 pings (the default Count value) + $pingResults.Count | Should -Be 4 + $pingResults[0].Address | Should -BeExactly $targetAddress + } } Context "MTUSizeDetect" { @@ -291,6 +300,16 @@ Describe "Test-Connection" -tags "CI", "RequireSudoOnUnix" { $result | Should -BeOfType Int32 $result | Should -BeGreaterThan 0 } + + It "MtuSize with explicit false should behave like default ping" { + # -MtuSize:$false should behave the same as not specifying -MtuSize (default ping) + $pingResults = Test-Connection $targetAddress -MtuSize:$false + + # Should return PingStatus, not PingMtuStatus + $pingResults[0] | Should -BeOfType Microsoft.PowerShell.Commands.TestConnectionCommand+PingStatus + # Should get 4 pings (the default Count value) + $pingResults.Count | Should -Be 4 + } } Context "TraceRoute" { @@ -332,6 +351,16 @@ Describe "Test-Connection" -tags "CI", "RequireSudoOnUnix" { $results.Hostname | Should -Not -BeNullOrEmpty } + + It "Traceroute with explicit false should behave like default ping" { + # -Traceroute:$false should behave the same as not specifying -Traceroute (default ping) + $pingResults = Test-Connection $targetAddress -Traceroute:$false + + # Should return PingStatus, not TraceStatus + $pingResults[0] | Should -BeOfType Microsoft.PowerShell.Commands.TestConnectionCommand+PingStatus + # Should get 4 pings (the default Count value) + $pingResults.Count | Should -Be 4 + } } } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/TimeZone.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/TimeZone.Tests.ps1 index 5a23c7df75a..a3904fca41f 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/TimeZone.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/TimeZone.Tests.ps1 @@ -71,6 +71,12 @@ Describe "Get-Timezone test cases" -Tags "CI" { $oneExpectedOffset | Should -BeIn $observedIdList } + It "Call with -ListAvailable:`$false returns current TimeZoneInfo (not list)" { + $result = Get-TimeZone -ListAvailable:$false + $result | Should -BeOfType TimeZoneInfo + $result.Id | Should -Be ([System.TimeZoneInfo]::Local).Id + } + It "Call Get-TimeZone using ID param and single item" { $selectedTZ = $TimeZonesAvailable[0] (Get-TimeZone -Id $selectedTZ.Id).Id | Should -Be $selectedTZ.Id diff --git a/test/powershell/Modules/Microsoft.PowerShell.PSResourceGet/Microsoft.PowerShell.PSResourceGet.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.PSResourceGet/Microsoft.PowerShell.PSResourceGet.Tests.ps1 index 46e4f60cbc2..9285281daec 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.PSResourceGet/Microsoft.PowerShell.PSResourceGet.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.PSResourceGet/Microsoft.PowerShell.PSResourceGet.Tests.ps1 @@ -239,7 +239,7 @@ Describe "PSResourceGet - Module tests (Admin)" -Tags @('Feature', 'RequireAdmin } It "Should install a module correctly to the required location with AllUsers scope" { - Install-PSResource -Name $TestModule -Repository $RepositoryName -Scope AllUsers + Install-PSResource -Name $TestModule -Repository $RepositoryName -Scope AllUsers -ErrorAction Stop $module = Get-Module $TestModule -ListAvailable $module.Name | Should -Be $TestModule @@ -323,7 +323,7 @@ Describe "PSResourceGet - Script tests (Admin)" -Tags @('Feature', 'RequireAdmin } It "Should install a script correctly to the required location with AllUsers scope" { - Install-PSResource -Name $TestScript -Repository $RepositoryName -Scope AllUsers + Install-PSResource -Name $TestScript -Repository $RepositoryName -Scope AllUsers -ErrorAction Stop } AfterAll { diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/AclCmdlets.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Security/AclCmdlets.Tests.ps1 index 0e50d7c3603..52938a0b881 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Security/AclCmdlets.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/AclCmdlets.Tests.ps1 @@ -3,6 +3,7 @@ Describe "Acl cmdlets are available and operate properly" -Tag CI { Context "Windows ACL test" { BeforeAll { + $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() $PSDefaultParameterValues["It:Skip"] = -not $IsWindows } @@ -103,7 +104,7 @@ Describe "Acl cmdlets are available and operate properly" -Tag CI { } AfterAll { - $PSDefaultParameterValues.Remove("It:Skip") + $global:PSDefaultParameterValues = $originalDefaultParameterValues } } } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/CmsMessage.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Security/CmsMessage.Tests.ps1 index 50cbdebab69..a40dfc8cfcd 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Security/CmsMessage.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/CmsMessage.Tests.ps1 @@ -1,5 +1,9 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. + +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] +param() + Import-Module (Join-Path -Path $PSScriptRoot 'certificateCommon.psm1') -Force Describe "CmsMessage cmdlets and Get-PfxCertificate basic tests" -Tags "CI" { diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/GetCredential.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Security/GetCredential.Tests.ps1 index cb9d0ee70ed..fad8285aab3 100755 --- a/test/powershell/Modules/Microsoft.PowerShell.Security/GetCredential.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/GetCredential.Tests.ps1 @@ -1,5 +1,9 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. + +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] +param() + Describe "Get-Credential Test" -Tag "CI" { BeforeAll { $th = New-TestHost diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/UserConfigProviderModVersion1/UserConfigProviderModVersion1.psm1 b/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/UserConfigProviderModVersion1/UserConfigProviderModVersion1.psm1 index fd85fef1552..45188075fbc 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/UserConfigProviderModVersion1/UserConfigProviderModVersion1.psm1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/UserConfigProviderModVersion1/UserConfigProviderModVersion1.psm1 @@ -5,56 +5,50 @@ # This cmdlet executes the user supplied script (i.e., the script is responsible for validating the desired state of the # DSC managed node). The result of the script execution is in the form of a hashtable containing all the information # gathered from the GetScript execution. -function Get-TargetResource -{ +function Get-TargetResource { [CmdletBinding()] - param - ( - [parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string] - $text - ) + param( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string] + $Text + ) $result = @{ - Text = "Hello from Get!"; - } - $result; + Text = "Hello from Get!" + } + + $result } # The Set-TargetResource cmdlet is used to Set the desired state of the DSC managed node through a powershell script. # The method executes the user supplied script (i.e., the script is responsible for validating the desired state of the # DSC managed node). If the DSC managed node requires a restart either during or after the execution of the SetScript, # the SetScript notifies the PS Infrastructure by setting the variable $DSCMachineStatus.IsRestartRequired to $true. -function Set-TargetResource -{ +function Set-TargetResource { [CmdletBinding()] - param - ( - [parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string] - $text - ) - $path = "$env:SystemDrive\dscTestPath\hello1.txt" - New-Item -Path $path -Type File -Force - Add-Content -Path $path -Value $text + param( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string] + $Text + ) + $path = "$env:SystemDrive\dscTestPath\hello1.txt" + New-Item -Path $path -Type File -Force + Add-Content -Path $path -Value $text } # The Test-TargetResource cmdlet is used to validate the desired state of the DSC managed node through a powershell script. # The method executes the user supplied script (i.e., the script is responsible for validating the desired state of the # DSC managed node). The result of the script execution should be true if the DSC managed machine is in the desired state # or else false should be returned. -function Test-TargetResource -{ +function Test-TargetResource { [CmdletBinding()] - param - ( - [parameter(Mandatory = $true)] + param( + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] - $text - ) - $false + $Text + ) + $false } - diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/UserConfigProviderModVersion2/UserConfigProviderModVersion2.psm1 b/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/UserConfigProviderModVersion2/UserConfigProviderModVersion2.psm1 index d2f6a9ac719..05dbdcec7e1 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/UserConfigProviderModVersion2/UserConfigProviderModVersion2.psm1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/UserConfigProviderModVersion2/UserConfigProviderModVersion2.psm1 @@ -5,57 +5,51 @@ # This cmdlet executes the user supplied script (i.e., the script is responsible for validating the desired state of the # DSC managed node). The result of the script execution is in the form of a hashtable containing all the information # gathered from the GetScript execution. -function Get-TargetResource -{ +function Get-TargetResource { [CmdletBinding()] - param - ( - [parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string] - $text - ) + param( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string] + $Text + ) $result = @{ - Text = "Hello from Get!"; - } - $result; - } + Text = "Hello from Get!" + } + + $result +} # The Set-TargetResource cmdlet is used to Set the desired state of the DSC managed node through a powershell script. # The method executes the user supplied script (i.e., the script is responsible for validating the desired state of the # DSC managed node). If the DSC managed node requires a restart either during or after the execution of the SetScript, # the SetScript notifies the PS Infrastructure by setting the variable $DSCMachineStatus.IsRestartRequired to $true. -function Set-TargetResource -{ +function Set-TargetResource { [CmdletBinding()] - param - ( - [parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string] - $text - ) + param( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string] + $Text + ) - $path = "$env:SystemDrive\dscTestPath\hello2.txt" - New-Item -Path $path -Type File -Force - Add-Content -Path $path -Value $text + $path = "$env:SystemDrive\dscTestPath\hello2.txt" + New-Item -Path $path -Type File -Force + Add-Content -Path $path -Value $text } # The Test-TargetResource cmdlet is used to validate the desired state of the DSC managed node through a powershell script. # The method executes the user supplied script (i.e., the script is responsible for validating the desired state of the # DSC managed node). The result of the script execution should be true if the DSC managed machine is in the desired state # or else false should be returned. -function Test-TargetResource -{ +function Test-TargetResource { [CmdletBinding()] - param - ( - [parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string] - $text - ) - $false + param( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string] + $Text + ) + $false } - diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/UserConfigProviderModVersion3/UserConfigProviderModVersion3.psm1 b/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/UserConfigProviderModVersion3/UserConfigProviderModVersion3.psm1 index 45987a71f76..134158d62a9 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/UserConfigProviderModVersion3/UserConfigProviderModVersion3.psm1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/TestData/CatalogTestData/UserConfigProv/DSCResources/UserConfigProviderModVersion3/UserConfigProviderModVersion3.psm1 @@ -5,57 +5,51 @@ # This cmdlet executes the user supplied script (i.e., the script is responsible for validating the desired state of the # DSC managed node). The result of the script execution is in the form of a hashtable containing all the information # gathered from the GetScript execution. -function Get-TargetResource -{ +function Get-TargetResource { [CmdletBinding()] - param - ( - [parameter(Mandatory = $true)] + param( + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] - $text - ) + $Text + ) $result = @{ - Text = "Hello from Get!"; - } - $result; - } + Text = "Hello from Get!" + } + + $result +} # The Set-TargetResource cmdlet is used to Set the desired state of the DSC managed node through a powershell script. # The method executes the user supplied script (i.e., the script is responsible for validating the desired state of the # DSC managed node). If the DSC managed node requires a restart either during or after the execution of the SetScript, # the SetScript notifies the PS Infrastructure by setting the variable $DSCMachineStatus.IsRestartRequired to $true. -function Set-TargetResource -{ +function Set-TargetResource { [CmdletBinding()] - param - ( - [parameter(Mandatory = $true)] + param( + [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string] - $text - ) + $Text + ) - $path = "$env:SystemDrive\dscTestPath\hello3.txt" - New-Item -Path $path -Type File -Force - Add-Content -Path $path -Value $text + $path = "$env:SystemDrive\dscTestPath\hello3.txt" + New-Item -Path $path -Type File -Force + Add-Content -Path $path -Value $text } # The Test-TargetResource cmdlet is used to validate the desired state of the DSC managed node through a powershell script. # The method executes the user supplied script (i.e., the script is responsible for validating the desired state of the # DSC managed node). The result of the script execution should be true if the DSC managed machine is in the desired state # or else false should be returned. -function Test-TargetResource -{ +function Test-TargetResource { [CmdletBinding()] - param - ( - [parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string] - $text - ) - $false + param( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string] + $Text + ) + $false } - diff --git a/test/powershell/Modules/Microsoft.PowerShell.Security/certificateCommon.psm1 b/test/powershell/Modules/Microsoft.PowerShell.Security/certificateCommon.psm1 index 5601767a120..46092386fe3 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Security/certificateCommon.psm1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/certificateCommon.psm1 @@ -1,6 +1,9 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] +param() + Function New-GoodCertificate { <# diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertFrom-Json.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertFrom-Json.Tests.ps1 index 599ff35639e..aebdc37d1c2 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertFrom-Json.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertFrom-Json.Tests.ps1 @@ -350,6 +350,38 @@ c 3 $json | Should -BeOfType ([string]) $json | Should -Be $Value.Substring(1, $Value.Length - 2) } + + It 'Ignores comments in arrays' -TestCase $testCasesWithAndWithoutAsHashtableSwitch { + param($AsHashtable) + + # https://github.com/powerShell/powerShell/issues/14553 + '[ + // comment + 100, + /* comment */ + 200 + ]' | ConvertFrom-Json -AsHashtable:$AsHashtable | Should -Be @(100, 200) + } + + It 'Ignores comments in dictionaries' -TestCase $testCasesWithAndWithoutAsHashtableSwitch { + param($AsHashtable) + + $json = '{ + // comment + "a": 100, + /* comment */ + "b": 200 + }' | ConvertFrom-Json -AsHashtable:$AsHashtable + + if ($AsHashtable) { + $json.Keys | Should -Be @("a", "b") + } else { + $json.psobject.Properties | Should -HaveCount 2 + } + + $json.a | Should -Be 100 + $json.b | Should -Be 200 + } } Describe 'ConvertFrom-Json -Depth Tests' -tags "Feature" { diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Csv.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Csv.Tests.ps1 index 0d484260813..bfba7718272 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Csv.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Csv.Tests.ps1 @@ -101,11 +101,6 @@ Describe "ConvertTo-Csv" -Tags "CI" { Should -Throw -ErrorId "CannotSpecifyQuoteFieldsAndUseQuotes,Microsoft.PowerShell.Commands.ConvertToCsvCommand" } - It "Does not support -IncludeTypeInformation and -NoTypeInformation at the same time" { - { $testObject | ConvertTo-Csv -IncludeTypeInformation -NoTypeInformation } | - Should -Throw -ErrorId "CannotSpecifyIncludeTypeInformationAndNoTypeInformation,Microsoft.PowerShell.Commands.ConvertToCsvCommand" - } - Context "QuoteFields parameter" { It "QuoteFields" { # Use 'FiRstCoLumn' to test case insensitivity @@ -212,6 +207,7 @@ Describe "ConvertTo-Csv" -Tags "CI" { [ordered]@{ Number = $_; Letter = $Letters[$_] } } $CsvString = $Items | ConvertTo-Csv + $TestHashTable = [ordered]@{ 'first' = 'value1'; 'second' = $null; 'third' = 'value3' } } It 'should treat dictionary entries as properties' { @@ -235,5 +231,12 @@ Describe "ConvertTo-Csv" -Tags "CI" { $NewCsvString[0] | Should -MatchExactly 'Extra' $NewCsvString | Select-Object -Skip 1 | Should -MatchExactly 'Surprise!' } + + It 'should properly convert hashtable with null and non-null values'{ + $CsvResult = $TestHashTable | ConvertTo-Csv + + $CsvResult[0] | Should -BeExactly "`"first`",`"second`",`"third`"" + $CsvResult[1] | Should -BeExactly "`"value1`",$null,`"value3`"" + } } } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Json.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Json.Tests.ps1 index 1f2abe05c68..f1ddc43a220 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Json.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Json.Tests.ps1 @@ -156,4 +156,1738 @@ Describe 'ConvertTo-Json' -tags "CI" { $actual = ConvertTo-Json -Compress -InputObject $obj $actual | Should -Be '{"Positive":18446744073709551615,"Negative":-18446744073709551615}' } + #region Comprehensive Scalar Type Tests (Phase 1) + # Test coverage for ConvertTo-Json scalar serialization + # Covers: Pipeline vs InputObject, ETS vs no ETS, all primitive and special types + + Context 'Primitive scalar types' { + It 'Should serialize <TypeName> value <Value> correctly via Pipeline and InputObject' -TestCases @( + # Byte types + @{ TypeName = 'byte'; Value = [byte]0; Expected = '0' } + @{ TypeName = 'byte'; Value = [byte]255; Expected = '255' } + @{ TypeName = 'sbyte'; Value = [sbyte]-128; Expected = '-128' } + @{ TypeName = 'sbyte'; Value = [sbyte]127; Expected = '127' } + # Short types + @{ TypeName = 'short'; Value = [short]-32768; Expected = '-32768' } + @{ TypeName = 'short'; Value = [short]32767; Expected = '32767' } + @{ TypeName = 'ushort'; Value = [ushort]0; Expected = '0' } + @{ TypeName = 'ushort'; Value = [ushort]65535; Expected = '65535' } + # Integer types + @{ TypeName = 'int'; Value = 42; Expected = '42' } + @{ TypeName = 'int'; Value = -42; Expected = '-42' } + @{ TypeName = 'int'; Value = 0; Expected = '0' } + @{ TypeName = 'int'; Value = [int]::MaxValue; Expected = '2147483647' } + @{ TypeName = 'int'; Value = [int]::MinValue; Expected = '-2147483648' } + @{ TypeName = 'uint'; Value = [uint]0; Expected = '0' } + @{ TypeName = 'uint'; Value = [uint]::MaxValue; Expected = '4294967295' } + # Long types + @{ TypeName = 'long'; Value = [long]::MaxValue; Expected = '9223372036854775807' } + @{ TypeName = 'long'; Value = [long]::MinValue; Expected = '-9223372036854775808' } + @{ TypeName = 'ulong'; Value = [ulong]0; Expected = '0' } + @{ TypeName = 'ulong'; Value = [ulong]::MaxValue; Expected = '18446744073709551615' } + # Floating-point types + @{ TypeName = 'float'; Value = [float]3.14; Expected = '3.14' } + @{ TypeName = 'float'; Value = [float]::NaN; Expected = '"NaN"' } + @{ TypeName = 'float'; Value = [float]::PositiveInfinity; Expected = '"Infinity"' } + @{ TypeName = 'float'; Value = [float]::NegativeInfinity; Expected = '"-Infinity"' } + @{ TypeName = 'double'; Value = 3.14159; Expected = '3.14159' } + @{ TypeName = 'double'; Value = -3.14159; Expected = '-3.14159' } + @{ TypeName = 'double'; Value = 0.0; Expected = '0.0' } + @{ TypeName = 'double'; Value = [double]::NaN; Expected = '"NaN"' } + @{ TypeName = 'double'; Value = [double]::PositiveInfinity; Expected = '"Infinity"' } + @{ TypeName = 'double'; Value = [double]::NegativeInfinity; Expected = '"-Infinity"' } + @{ TypeName = 'decimal'; Value = 123.456d; Expected = '123.456' } + # BigInteger + @{ TypeName = 'BigInteger'; Value = 18446744073709551615n; Expected = '18446744073709551615' } + # Boolean + @{ TypeName = 'bool'; Value = $true; Expected = 'true' } + @{ TypeName = 'bool'; Value = $false; Expected = 'false' } + # Null + @{ TypeName = 'null'; Value = $null; Expected = 'null' } + ) { + param($TypeName, $Value, $Expected) + $jsonPipeline = $Value | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $Value -Compress + $jsonPipeline | Should -BeExactly $Expected + $jsonInputObject | Should -BeExactly $Expected + } + + It 'Should include ETS properties on <TypeName>' -TestCases @( + @{ TypeName = 'int'; Value = 42; Expected = '{"value":42,"MyProp":"test"}' } + @{ TypeName = 'double'; Value = 3.14; Expected = '{"value":3.14,"MyProp":"test"}' } + ) { + param($TypeName, $Value, $Expected) + $valueWithEts = Add-Member -InputObject $Value -MemberType NoteProperty -Name MyProp -Value 'test' -PassThru + $json = $valueWithEts | ConvertTo-Json -Compress + $json | Should -BeExactly $Expected + } + } + + Context 'String scalar types' { + It 'Should serialize string <Description> correctly via Pipeline and InputObject' -TestCases @( + @{ Description = 'regular'; Value = 'hello'; Expected = '"hello"' } + @{ Description = 'empty'; Value = ''; Expected = '""' } + @{ Description = 'with spaces'; Value = 'hello world'; Expected = '"hello world"' } + @{ Description = 'with newline'; Value = "line1`nline2"; Expected = '"line1\nline2"' } + @{ Description = 'with tab'; Value = "col1`tcol2"; Expected = '"col1\tcol2"' } + @{ Description = 'with quotes'; Value = 'say "hello"'; Expected = '"say \"hello\""' } + @{ Description = 'with backslash'; Value = 'c:\path'; Expected = '"c:\\path"' } + @{ Description = 'unicode'; Value = '???'; Expected = '"???"' } + @{ Description = 'emoji'; Value = '??'; Expected = '"??"' } + ) { + param($Description, $Value, $Expected) + $jsonPipeline = $Value | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $Value -Compress + $jsonPipeline | Should -BeExactly $Expected + $jsonInputObject | Should -BeExactly $Expected + } + + It 'Should ignore ETS properties on string' { + $str = Add-Member -InputObject 'hello' -MemberType NoteProperty -Name MyProp -Value 'test' -PassThru + $json = $str | ConvertTo-Json -Compress + $json | Should -BeExactly '"hello"' + } + } + + Context 'DateTime and related types' { + It 'Should serialize DateTime with UTC kind via Pipeline and InputObject' { + $dt = [DateTime]::new(2024, 6, 15, 10, 30, 0, [DateTimeKind]::Utc) + $jsonPipeline = $dt | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $dt -Compress + $jsonPipeline | Should -BeExactly '"2024-06-15T10:30:00Z"' + $jsonInputObject | Should -BeExactly '"2024-06-15T10:30:00Z"' + } + + It 'Should serialize DateTime with Local kind' { + $dt = [DateTime]::new(2024, 6, 15, 10, 30, 0, [DateTimeKind]::Local) + $json = $dt | ConvertTo-Json -Compress + $offset = $dt.ToString('zzz') + $expected = '"2024-06-15T10:30:00' + $offset + '"' + $json | Should -BeExactly $expected + } + + It 'Should serialize DateTime with Unspecified kind via Pipeline and InputObject' { + $dt = [DateTime]::new(2024, 6, 15, 10, 30, 0, [DateTimeKind]::Unspecified) + $jsonPipeline = $dt | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $dt -Compress + $jsonPipeline | Should -BeExactly '"2024-06-15T10:30:00"' + $jsonInputObject | Should -BeExactly '"2024-06-15T10:30:00"' + } + + It 'Should serialize DateTimeOffset correctly via Pipeline and InputObject' { + $dto = [DateTimeOffset]::new(2024, 6, 15, 10, 30, 0, [TimeSpan]::FromHours(9)) + $jsonPipeline = $dto | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $dto -Compress + $jsonPipeline | Should -BeExactly '"2024-06-15T10:30:00+09:00"' + $jsonInputObject | Should -BeExactly '"2024-06-15T10:30:00+09:00"' + } + + It 'Should serialize DateOnly as object with properties' { + $d = [DateOnly]::new(2024, 6, 15) + $json = $d | ConvertTo-Json -Compress + $json | Should -BeExactly '{"Year":2024,"Month":6,"Day":15,"DayOfWeek":6,"DayOfYear":167,"DayNumber":739051}' + } + + It 'Should serialize TimeOnly as object with properties' { + $t = [TimeOnly]::new(10, 30, 45) + $json = $t | ConvertTo-Json -Compress + $json | Should -BeExactly '{"Hour":10,"Minute":30,"Second":45,"Millisecond":0,"Microsecond":0,"Nanosecond":0,"Ticks":378450000000}' + } + + It 'Should serialize TimeSpan as object with properties' { + $ts = [TimeSpan]::new(1, 2, 3, 4, 5) + $json = $ts | ConvertTo-Json -Compress + $json | Should -BeExactly '{"Ticks":937840050000,"Days":1,"Hours":2,"Milliseconds":5,"Microseconds":0,"Nanoseconds":0,"Minutes":3,"Seconds":4,"TotalDays":1.0854630208333333,"TotalHours":26.0511125,"TotalMilliseconds":93784005.0,"TotalMicroseconds":93784005000.0,"TotalNanoseconds":93784005000000.0,"TotalMinutes":1563.06675,"TotalSeconds":93784.005}' + } + + It 'Should ignore ETS properties on DateTime' { + $dt = [DateTime]::new(2024, 6, 15, 0, 0, 0, [DateTimeKind]::Utc) + $dt = Add-Member -InputObject $dt -MemberType NoteProperty -Name MyProp -Value 'test' -PassThru + $json = $dt | ConvertTo-Json -Compress + $json | Should -BeExactly '"2024-06-15T00:00:00Z"' + } + + It 'Should include ETS properties on DateTimeOffset' { + $dto = [DateTimeOffset]::new(2024, 6, 15, 10, 30, 0, [TimeSpan]::Zero) + $dto = Add-Member -InputObject $dto -MemberType NoteProperty -Name MyProp -Value 'test' -PassThru + $json = $dto | ConvertTo-Json -Compress + $json | Should -BeExactly '{"value":"2024-06-15T10:30:00+00:00","MyProp":"test"}' + } + + It 'Should include ETS properties on DateOnly' { + $d = [DateOnly]::new(2024, 6, 15) + $d = Add-Member -InputObject $d -MemberType NoteProperty -Name MyProp -Value 'test' -PassThru + $json = $d | ConvertTo-Json -Compress + $json | Should -BeExactly '{"Year":2024,"Month":6,"Day":15,"DayOfWeek":6,"DayOfYear":167,"DayNumber":739051,"MyProp":"test"}' + } + + It 'Should include ETS properties on TimeOnly' { + $t = [TimeOnly]::new(10, 30, 45) + $t = Add-Member -InputObject $t -MemberType NoteProperty -Name MyProp -Value 'test' -PassThru + $json = $t | ConvertTo-Json -Compress + $json | Should -BeExactly '{"Hour":10,"Minute":30,"Second":45,"Millisecond":0,"Microsecond":0,"Nanosecond":0,"Ticks":378450000000,"MyProp":"test"}' + } + } + + Context 'Guid type' { + It 'Should serialize Guid as string via InputObject' { + $guid = [Guid]::new('12345678-1234-1234-1234-123456789abc') + $json = ConvertTo-Json -InputObject $guid -Compress + $json | Should -BeExactly '"12345678-1234-1234-1234-123456789abc"' + } + + It 'Should serialize Guid with Extended properties via Pipeline' { + $guid = [Guid]::new('12345678-1234-1234-1234-123456789abc') + $json = $guid | ConvertTo-Json -Compress + $json | Should -BeExactly '{"value":"12345678-1234-1234-1234-123456789abc","Guid":"12345678-1234-1234-1234-123456789abc"}' + } + + It 'Should serialize empty Guid correctly via InputObject' { + $json = ConvertTo-Json -InputObject ([Guid]::Empty) -Compress + $json | Should -BeExactly '"00000000-0000-0000-0000-000000000000"' + } + + It 'Should include ETS properties on Guid via Pipeline' { + $guid = [Guid]::new('12345678-1234-1234-1234-123456789abc') + $guid = Add-Member -InputObject $guid -MemberType NoteProperty -Name MyProp -Value 'test' -PassThru + $json = $guid | ConvertTo-Json -Compress + $json | Should -BeExactly '{"value":"12345678-1234-1234-1234-123456789abc","MyProp":"test","Guid":"12345678-1234-1234-1234-123456789abc"}' + } + } + + Context 'Uri type' { + It 'Should serialize Uri <Description> correctly via Pipeline and InputObject' -TestCases @( + @{ Description = 'http'; UriString = 'http://example.com'; Expected = '"http://example.com"' } + @{ Description = 'https with path'; UriString = 'https://example.com/path'; Expected = '"https://example.com/path"' } + @{ Description = 'with query'; UriString = 'https://example.com/search?q=test'; Expected = '"https://example.com/search?q=test"' } + @{ Description = 'file'; UriString = 'file:///c:/temp/file.txt'; Expected = '"file:///c:/temp/file.txt"' } + ) { + param($Description, $UriString, $Expected) + $uri = [Uri]$UriString + $jsonPipeline = $uri | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $uri -Compress + $jsonPipeline | Should -BeExactly $Expected + $jsonInputObject | Should -BeExactly $Expected + } + + It 'Should include ETS properties on Uri' { + $uri = [Uri]'https://example.com' + $uri = Add-Member -InputObject $uri -MemberType NoteProperty -Name MyProp -Value 'test' -PassThru + $json = $uri | ConvertTo-Json -Compress + $json | Should -BeExactly '{"value":"https://example.com","MyProp":"test"}' + } + } + + Context 'Enum types' { + It 'Should serialize enum <EnumType>::<Value> as <Expected> via Pipeline and InputObject' -TestCases @( + @{ EnumType = 'System.DayOfWeek'; Value = 'Sunday'; Expected = '0' } + @{ EnumType = 'System.DayOfWeek'; Value = 'Monday'; Expected = '1' } + @{ EnumType = 'System.DayOfWeek'; Value = 'Saturday'; Expected = '6' } + @{ EnumType = 'System.ConsoleColor'; Value = 'Red'; Expected = '12' } + @{ EnumType = 'System.IO.FileAttributes'; Value = 'ReadOnly'; Expected = '1' } + @{ EnumType = 'System.IO.FileAttributes'; Value = 'Hidden'; Expected = '2' } + ) { + param($EnumType, $Value, $Expected) + $enumValue = [Enum]::Parse($EnumType, $Value) + $jsonPipeline = $enumValue | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $enumValue -Compress + $jsonPipeline | Should -BeExactly $Expected + $jsonInputObject | Should -BeExactly $Expected + } + + It 'Should serialize enum as "<Expected>" with -EnumsAsStrings' -TestCases @( + @{ EnumType = 'System.DayOfWeek'; Value = 'Sunday'; Expected = 'Sunday' } + @{ EnumType = 'System.DayOfWeek'; Value = 'Monday'; Expected = 'Monday' } + @{ EnumType = 'System.ConsoleColor'; Value = 'Red'; Expected = 'Red' } + ) { + param($EnumType, $Value, $Expected) + $enumValue = [Enum]::Parse($EnumType, $Value) + $json = $enumValue | ConvertTo-Json -Compress -EnumsAsStrings + $json | Should -BeExactly "`"$Expected`"" + } + + It 'Should serialize flags enum correctly via Pipeline and InputObject' { + $flags = [System.IO.FileAttributes]::ReadOnly -bor [System.IO.FileAttributes]::Hidden + $jsonPipeline = $flags | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $flags -Compress + $jsonPipeline | Should -BeExactly '3' + $jsonInputObject | Should -BeExactly '3' + } + + It 'Should serialize flags enum as string with -EnumsAsStrings' { + $flags = [System.IO.FileAttributes]::ReadOnly -bor [System.IO.FileAttributes]::Hidden + $json = $flags | ConvertTo-Json -Compress -EnumsAsStrings + $json | Should -BeExactly '"ReadOnly, Hidden"' + } + + It 'Should include ETS properties on Enum' { + $enum = Add-Member -InputObject ([DayOfWeek]::Monday) -MemberType NoteProperty -Name MyProp -Value 'test' -PassThru + $json = $enum | ConvertTo-Json -Compress + $json | Should -BeExactly '{"value":1,"MyProp":"test"}' + } + } + + Context 'IPAddress type' { + It 'Should serialize IPAddress v4 correctly via InputObject' { + $ip = [System.Net.IPAddress]::Parse('192.168.1.1') + $json = ConvertTo-Json -InputObject $ip -Compress + $json | Should -BeExactly '{"AddressFamily":2,"ScopeId":null,"IsIPv6Multicast":false,"IsIPv6LinkLocal":false,"IsIPv6SiteLocal":false,"IsIPv6Teredo":false,"IsIPv6UniqueLocal":false,"IsIPv4MappedToIPv6":false,"Address":16885952}' + } + + It 'Should serialize IPAddress v4 correctly via Pipeline' { + $ip = [System.Net.IPAddress]::Parse('192.168.1.1') + $json = $ip | ConvertTo-Json -Compress + $json | Should -BeExactly '{"AddressFamily":2,"ScopeId":null,"IsIPv6Multicast":false,"IsIPv6LinkLocal":false,"IsIPv6SiteLocal":false,"IsIPv6Teredo":false,"IsIPv6UniqueLocal":false,"IsIPv4MappedToIPv6":false,"Address":16885952,"IPAddressToString":"192.168.1.1"}' + } + + It 'Should serialize IPAddress v6 correctly via InputObject' { + $ip = [System.Net.IPAddress]::Parse('::1') + $json = ConvertTo-Json -InputObject $ip -Compress + $json | Should -BeExactly '{"AddressFamily":23,"ScopeId":0,"IsIPv6Multicast":false,"IsIPv6LinkLocal":false,"IsIPv6SiteLocal":false,"IsIPv6Teredo":false,"IsIPv6UniqueLocal":false,"IsIPv4MappedToIPv6":false,"Address":null}' + } + + It 'Should serialize IPAddress v6 correctly via Pipeline' { + $ip = [System.Net.IPAddress]::Parse('::1') + $json = $ip | ConvertTo-Json -Compress + $json | Should -BeExactly '{"AddressFamily":23,"ScopeId":0,"IsIPv6Multicast":false,"IsIPv6LinkLocal":false,"IsIPv6SiteLocal":false,"IsIPv6Teredo":false,"IsIPv6UniqueLocal":false,"IsIPv4MappedToIPv6":false,"Address":null,"IPAddressToString":"::1"}' + } + + It 'Should include ETS properties on IPAddress' { + $ip = [System.Net.IPAddress]::Parse('192.168.1.1') + $ip = Add-Member -InputObject $ip -MemberType NoteProperty -Name MyProp -Value 'test' -PassThru + $json = $ip | ConvertTo-Json -Compress + $json | Should -BeExactly '{"AddressFamily":2,"ScopeId":null,"IsIPv6Multicast":false,"IsIPv6LinkLocal":false,"IsIPv6SiteLocal":false,"IsIPv6Teredo":false,"IsIPv6UniqueLocal":false,"IsIPv4MappedToIPv6":false,"Address":16885952,"MyProp":"test","IPAddressToString":"192.168.1.1"}' + } + } + + Context 'Scalars as elements of arrays' { + It 'Should serialize array of <TypeName> correctly via Pipeline and InputObject' -TestCases @( + @{ TypeName = 'int'; Values = @(1, 2, 3); Expected = '[1,2,3]' } + @{ TypeName = 'string'; Values = @('a', 'b', 'c'); Expected = '["a","b","c"]' } + @{ TypeName = 'double'; Values = @(1.1, 2.2, 3.3); Expected = '[1.1,2.2,3.3]' } + ) { + param($TypeName, $Values, $Expected) + $jsonPipeline = $Values | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $Values -Compress + $jsonPipeline | Should -BeExactly $Expected + $jsonInputObject | Should -BeExactly $Expected + } + + # Note: bool array test uses InputObject only because $true/$false are singletons + # and ETS properties added in other tests would affect Pipeline serialization + It 'Should serialize array of bool correctly via InputObject' { + $bools = @($true, $false, $true) + $json = ConvertTo-Json -InputObject $bools -Compress + $json | Should -BeExactly '[true,false,true]' + } + + It 'Should serialize array of Guid with Extended properties via Pipeline' { + $guids = @( + [Guid]'11111111-1111-1111-1111-111111111111', + [Guid]'22222222-2222-2222-2222-222222222222' + ) + $json = $guids | ConvertTo-Json -Compress + $json | Should -BeExactly '[{"value":"11111111-1111-1111-1111-111111111111","Guid":"11111111-1111-1111-1111-111111111111"},{"value":"22222222-2222-2222-2222-222222222222","Guid":"22222222-2222-2222-2222-222222222222"}]' + } + + It 'Should serialize array of enum correctly via Pipeline and InputObject' { + $enums = @([DayOfWeek]::Monday, [DayOfWeek]::Wednesday, [DayOfWeek]::Friday) + $jsonPipeline = $enums | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $enums -Compress + $jsonPipeline | Should -BeExactly '[1,3,5]' + $jsonInputObject | Should -BeExactly '[1,3,5]' + } + + It 'Should serialize array of enum as strings with -EnumsAsStrings' { + $enums = @([DayOfWeek]::Monday, [DayOfWeek]::Wednesday, [DayOfWeek]::Friday) + $json = $enums | ConvertTo-Json -Compress -EnumsAsStrings + $json | Should -BeExactly '["Monday","Wednesday","Friday"]' + } + + # Note: mixed array test uses InputObject only due to $true singleton issue + It 'Should serialize mixed type array correctly via InputObject' { + $mixed = @(1, 'two', $true, 3.14) + $json = ConvertTo-Json -InputObject $mixed -Compress + $json | Should -BeExactly '[1,"two",true,3.14]' + } + + It 'Should serialize array with null elements correctly' { + $arr = @(1, $null, 'three') + $json = $arr | ConvertTo-Json -Compress + $json | Should -BeExactly '[1,null,"three"]' + } + + It 'Should include ETS properties on array via InputObject' { + $arr = @(1, 2, 3) + $arr = Add-Member -InputObject $arr -MemberType NoteProperty -Name MyProp -Value 'test' -PassThru + $json = ConvertTo-Json -InputObject $arr -Compress + $json | Should -BeExactly '{"value":[1,2,3],"MyProp":"test"}' + } + } + + Context 'Scalars as values in hashtables and PSCustomObject' { + It 'Should serialize hashtable with scalar values correctly via Pipeline and InputObject' { + $hash = [ordered]@{ + intVal = 42 + strVal = 'hello' + boolVal = $true + doubleVal = 3.14 + nullVal = $null + } + $expected = '{"intVal":42,"strVal":"hello","boolVal":true,"doubleVal":3.14,"nullVal":null}' + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize PSCustomObject with scalar values correctly via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + intVal = 42 + strVal = 'hello' + boolVal = $true + doubleVal = 3.14 + } + $expected = '{"intVal":42,"strVal":"hello","boolVal":true,"doubleVal":3.14}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize hashtable with <TypeName> value correctly' -TestCases @( + @{ TypeName = 'DateTime'; Value = [DateTime]::new(2024, 6, 15, 10, 30, 0, [DateTimeKind]::Utc); Expected = '{"val":"2024-06-15T10:30:00Z"}' } + @{ TypeName = 'Guid'; Value = [Guid]'12345678-1234-1234-1234-123456789abc'; Expected = '{"val":"12345678-1234-1234-1234-123456789abc"}' } + @{ TypeName = 'Enum'; Value = [DayOfWeek]::Monday; Expected = '{"val":1}' } + @{ TypeName = 'Uri'; Value = [Uri]'https://example.com'; Expected = '{"val":"https://example.com"}' } + @{ TypeName = 'BigInteger'; Value = 18446744073709551615n; Expected = '{"val":18446744073709551615}' } + ) { + param($TypeName, $Value, $Expected) + $hash = @{ val = $Value } + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly $Expected + $jsonInputObject | Should -BeExactly $Expected + } + + It 'Should serialize hashtable with enum as string correctly' { + $hash = @{ day = [DayOfWeek]::Monday } + $json = $hash | ConvertTo-Json -Compress -EnumsAsStrings + $json | Should -BeExactly '{"day":"Monday"}' + } + + It 'Should include ETS properties on hashtable via InputObject' { + $hash = @{ a = 1 } + $hash = Add-Member -InputObject $hash -MemberType NoteProperty -Name MyProp -Value 'test' -PassThru + $json = ConvertTo-Json -InputObject $hash -Compress + $json | Should -BeExactly '{"a":1,"MyProp":"test"}' + } + } + + #endregion Comprehensive Scalar Type Tests (Phase 1) + + #region Comprehensive Array and Dictionary Tests (Phase 2) + # Test coverage for ConvertTo-Json array and dictionary serialization + # Covers: Pipeline vs InputObject, ETS vs no ETS, nested structures + + Context 'Array basic serialization' { + It 'Should serialize empty array correctly via Pipeline and InputObject' { + $arr = @() + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly '[]' + $jsonInputObject | Should -BeExactly '[]' + } + + It 'Should serialize single element array correctly via Pipeline and InputObject' { + $arr = @(42) + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly '[42]' + $jsonInputObject | Should -BeExactly '[42]' + } + + It 'Should serialize multi-element array correctly via Pipeline and InputObject' { + $arr = @(1, 2, 3, 4, 5) + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly '[1,2,3,4,5]' + $jsonInputObject | Should -BeExactly '[1,2,3,4,5]' + } + + It 'Should serialize string array correctly via Pipeline and InputObject' { + $arr = @('apple', 'banana', 'cherry') + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly '["apple","banana","cherry"]' + $jsonInputObject | Should -BeExactly '["apple","banana","cherry"]' + } + + It 'Should serialize typed array correctly via Pipeline and InputObject' { + [int[]]$arr = @(10, 20, 30) + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly '[10,20,30]' + $jsonInputObject | Should -BeExactly '[10,20,30]' + } + + It 'Should serialize array with single null element correctly via Pipeline and InputObject' { + $arr = @($null) + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly '[null]' + $jsonInputObject | Should -BeExactly '[null]' + } + + It 'Should serialize array with multiple null elements correctly via Pipeline and InputObject' { + $arr = @($null, $null, $null) + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly '[null,null,null]' + $jsonInputObject | Should -BeExactly '[null,null,null]' + } + } + + Context 'Nested arrays' { + It 'Should serialize 2D array correctly via Pipeline and InputObject' { + $arr = @(@(1, 2), @(3, 4)) + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly '[[1,2],[3,4]]' + $jsonInputObject | Should -BeExactly '[[1,2],[3,4]]' + } + + It 'Should serialize 3D array correctly via Pipeline and InputObject' { + $arr = @(@(@(1, 2), @(3, 4)), @(@(5, 6), @(7, 8))) + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly '[[[1,2],[3,4]],[[5,6],[7,8]]]' + $jsonInputObject | Should -BeExactly '[[[1,2],[3,4]],[[5,6],[7,8]]]' + } + + It 'Should serialize jagged array correctly via Pipeline and InputObject' { + $arr = @(@(1), @(2, 3), @(4, 5, 6)) + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly '[[1],[2,3],[4,5,6]]' + $jsonInputObject | Should -BeExactly '[[1],[2,3],[4,5,6]]' + } + + It 'Should serialize array containing empty arrays correctly via Pipeline and InputObject' { + $arr = @(@(), @(1), @()) + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly '[[],[1],[]]' + $jsonInputObject | Should -BeExactly '[[],[1],[]]' + } + + It 'Should serialize deeply nested array with Depth limit using ToString via Pipeline and InputObject' { + $ip = [System.Net.IPAddress]::Parse('192.168.1.1') + $arr = ,(,(,(,($ip)))) + $jsonPipeline = ,$arr | ConvertTo-Json -Compress -Depth 2 + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress -Depth 2 + $jsonPipeline | Should -BeExactly '[[["192.168.1.1"]]]' + $jsonInputObject | Should -BeExactly '[[["192.168.1.1"]]]' + } + + It 'Should serialize deeply nested array with sufficient Depth as full object via Pipeline and InputObject' { + $ip = [System.Net.IPAddress]::Parse('192.168.1.1') + $arr = ,(,(,(,($ip)))) + $expected = '[[[[{"AddressFamily":2,"ScopeId":null,"IsIPv6Multicast":false,"IsIPv6LinkLocal":false,"IsIPv6SiteLocal":false,"IsIPv6Teredo":false,"IsIPv6UniqueLocal":false,"IsIPv4MappedToIPv6":false,"Address":16885952}]]]]' + $jsonPipeline = ,$arr | ConvertTo-Json -Compress -Depth 10 + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress -Depth 10 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + } + + Context 'Array with mixed content types' { + It 'Should serialize array with mixed scalars correctly via Pipeline and InputObject' { + $arr = @(1, 'two', 3.14, $true, $null) + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly '[1,"two",3.14,true,null]' + $jsonInputObject | Should -BeExactly '[1,"two",3.14,true,null]' + } + + It 'Should serialize array with nested array and scalars correctly via Pipeline and InputObject' { + $arr = @(1, @(2, 3), 4) + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly '[1,[2,3],4]' + $jsonInputObject | Should -BeExactly '[1,[2,3],4]' + } + + It 'Should serialize array with PSCustomObject elements correctly via Pipeline and InputObject' { + $arr = @([PSCustomObject]@{x = 1}, [PSCustomObject]@{y = 2}) + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly '[{"x":1},{"y":2}]' + $jsonInputObject | Should -BeExactly '[{"x":1},{"y":2}]' + } + + It 'Should serialize array with DateTime elements correctly via Pipeline and InputObject' { + $date1 = [DateTime]::new(2024, 6, 15, 10, 30, 0, [DateTimeKind]::Utc) + $date2 = [DateTime]::new(2024, 12, 25, 0, 0, 0, [DateTimeKind]::Utc) + $arr = @($date1, $date2) + $expected = '["2024-06-15T10:30:00Z","2024-12-25T00:00:00Z"]' + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize array with Guid elements correctly via Pipeline and InputObject' { + $guid1 = [Guid]'12345678-1234-1234-1234-123456789abc' + $guid2 = [Guid]'87654321-4321-4321-4321-cba987654321' + $arr = @($guid1, $guid2) + $expected = '["12345678-1234-1234-1234-123456789abc","87654321-4321-4321-4321-cba987654321"]' + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize array with enum elements correctly via Pipeline and InputObject' { + $arr = @([DayOfWeek]::Monday, [DayOfWeek]::Friday) + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly '[1,5]' + $jsonInputObject | Should -BeExactly '[1,5]' + } + + It 'Should serialize array with enum as string correctly via Pipeline and InputObject' { + $arr = @([DayOfWeek]::Monday, [DayOfWeek]::Friday) + $jsonPipeline = ,$arr | ConvertTo-Json -Compress -EnumsAsStrings + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress -EnumsAsStrings + $jsonPipeline | Should -BeExactly '["Monday","Friday"]' + $jsonInputObject | Should -BeExactly '["Monday","Friday"]' + } + } + + Context 'Array ETS properties' { + It 'Should include ETS properties on array via Pipeline and InputObject' { + $arr = @(1, 2, 3) + $arr = Add-Member -InputObject $arr -MemberType NoteProperty -Name ArrayName -Value 'MyArray' -PassThru + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly '{"value":[1,2,3],"ArrayName":"MyArray"}' + $jsonInputObject | Should -BeExactly '{"value":[1,2,3],"ArrayName":"MyArray"}' + } + + It 'Should include multiple ETS properties on array via Pipeline and InputObject' { + $arr = @('a', 'b') + $arr = Add-Member -InputObject $arr -MemberType NoteProperty -Name Prop1 -Value 'val1' -PassThru + $arr = Add-Member -InputObject $arr -MemberType NoteProperty -Name Prop2 -Value 'val2' -PassThru + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly '{"value":["a","b"],"Prop1":"val1","Prop2":"val2"}' + $jsonInputObject | Should -BeExactly '{"value":["a","b"],"Prop1":"val1","Prop2":"val2"}' + } + } + + Context 'Hashtable basic serialization' { + It 'Should serialize empty hashtable correctly via Pipeline and InputObject' { + $hash = @{} + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly '{}' + $jsonInputObject | Should -BeExactly '{}' + } + + It 'Should serialize single key hashtable correctly via Pipeline and InputObject' { + $hash = @{ key = 'value' } + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly '{"key":"value"}' + $jsonInputObject | Should -BeExactly '{"key":"value"}' + } + + It 'Should serialize hashtable with null value correctly via Pipeline and InputObject' { + $hash = @{ nullKey = $null } + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly '{"nullKey":null}' + $jsonInputObject | Should -BeExactly '{"nullKey":null}' + } + + It 'Should serialize hashtable with various scalar types correctly via Pipeline and InputObject' { + $hash = [ordered]@{ + intKey = 42 + strKey = 'hello' + boolKey = $true + doubleKey = 3.14 + } + $expected = '{"intKey":42,"strKey":"hello","boolKey":true,"doubleKey":3.14}' + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + } + + Context 'OrderedDictionary serialization' { + It 'Should preserve order in OrderedDictionary via Pipeline and InputObject' { + $ordered = [ordered]@{ + z = 1 + a = 2 + m = 3 + } + $expected = '{"z":1,"a":2,"m":3}' + $jsonPipeline = $ordered | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $ordered -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize large OrderedDictionary preserving order via Pipeline and InputObject' { + $ordered = [ordered]@{} + 1..5 | ForEach-Object { $ordered["key$_"] = $_ } + $expected = '{"key1":1,"key2":2,"key3":3,"key4":4,"key5":5}' + $jsonPipeline = $ordered | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $ordered -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + } + + Context 'Nested dictionaries' { + It 'Should serialize nested hashtable correctly via Pipeline and InputObject' { + $hash = @{ + outer = @{ + inner = 'value' + } + } + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly '{"outer":{"inner":"value"}}' + $jsonInputObject | Should -BeExactly '{"outer":{"inner":"value"}}' + } + + It 'Should serialize deeply nested hashtable correctly via Pipeline and InputObject' { + $hash = @{ + level1 = @{ + level2 = @{ + level3 = 'deep' + } + } + } + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly '{"level1":{"level2":{"level3":"deep"}}}' + $jsonInputObject | Should -BeExactly '{"level1":{"level2":{"level3":"deep"}}}' + } + + It 'Should serialize nested hashtable with Depth limit via Pipeline and InputObject' { + $hash = @{ + level1 = @{ + level2 = @{ + level3 = 'deep' + } + } + } + $jsonPipeline = $hash | ConvertTo-Json -Compress -Depth 1 + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress -Depth 1 + $jsonPipeline | Should -BeExactly '{"level1":{"level2":"System.Collections.Hashtable"}}' + $jsonInputObject | Should -BeExactly '{"level1":{"level2":"System.Collections.Hashtable"}}' + } + + It 'Should serialize hashtable with array value correctly via Pipeline and InputObject' { + $hash = @{ arr = @(1, 2, 3) } + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly '{"arr":[1,2,3]}' + $jsonInputObject | Should -BeExactly '{"arr":[1,2,3]}' + } + + It 'Should serialize hashtable with nested array of hashtables correctly via Pipeline and InputObject' { + $hash = @{ + items = @( + @{ id = 1 }, + @{ id = 2 } + ) + } + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly '{"items":[{"id":1},{"id":2}]}' + $jsonInputObject | Should -BeExactly '{"items":[{"id":1},{"id":2}]}' + } + } + + Context 'Dictionary key types' { + It 'Should serialize hashtable with string keys correctly via Pipeline and InputObject' { + $hash = @{ 'string-key' = 'value' } + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly '{"string-key":"value"}' + $jsonInputObject | Should -BeExactly '{"string-key":"value"}' + } + + It 'Should serialize hashtable with special character keys correctly via Pipeline and InputObject' { + $hash = [ordered]@{ + 'key with space' = 1 + 'key-with-dash' = 2 + 'key_with_underscore' = 3 + } + $expected = '{"key with space":1,"key-with-dash":2,"key_with_underscore":3}' + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize hashtable with unicode keys correctly via Pipeline and InputObject' { + $hash = @{ "`u{65E5}`u{672C}`u{8A9E}" = 'Japanese' } + $expected = "{`"`u{65E5}`u{672C}`u{8A9E}`":`"Japanese`"}" + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize hashtable with empty string key correctly via Pipeline and InputObject' { + $hash = @{ '' = 'empty key' } + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly '{"":"empty key"}' + $jsonInputObject | Should -BeExactly '{"":"empty key"}' + } + } + + Context 'Dictionary with complex values' { + It 'Should serialize hashtable with DateTime value correctly via Pipeline and InputObject' { + $hash = @{ date = [DateTime]::new(2024, 6, 15, 10, 30, 0, [DateTimeKind]::Utc) } + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly '{"date":"2024-06-15T10:30:00Z"}' + $jsonInputObject | Should -BeExactly '{"date":"2024-06-15T10:30:00Z"}' + } + + It 'Should serialize hashtable with Guid value correctly via Pipeline and InputObject' { + $hash = @{ guid = [Guid]'12345678-1234-1234-1234-123456789abc' } + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly '{"guid":"12345678-1234-1234-1234-123456789abc"}' + $jsonInputObject | Should -BeExactly '{"guid":"12345678-1234-1234-1234-123456789abc"}' + } + + It 'Should serialize hashtable with enum value correctly via Pipeline and InputObject' { + $hash = @{ day = [DayOfWeek]::Monday } + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly '{"day":1}' + $jsonInputObject | Should -BeExactly '{"day":1}' + } + + It 'Should serialize hashtable with enum as string correctly via Pipeline and InputObject' { + $hash = @{ day = [DayOfWeek]::Monday } + $jsonPipeline = $hash | ConvertTo-Json -Compress -EnumsAsStrings + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress -EnumsAsStrings + $jsonPipeline | Should -BeExactly '{"day":"Monday"}' + $jsonInputObject | Should -BeExactly '{"day":"Monday"}' + } + + It 'Should serialize hashtable with PSCustomObject value correctly via Pipeline and InputObject' { + $hash = @{ obj = [PSCustomObject]@{ prop = 'value' } } + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly '{"obj":{"prop":"value"}}' + $jsonInputObject | Should -BeExactly '{"obj":{"prop":"value"}}' + } + } + + Context 'Dictionary ETS properties' { + It 'Should include ETS properties on hashtable via Pipeline and InputObject' { + $hash = @{ a = 1 } + $hash = Add-Member -InputObject $hash -MemberType NoteProperty -Name ETSProp -Value 'ets' -PassThru + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly '{"a":1,"ETSProp":"ets"}' + $jsonInputObject | Should -BeExactly '{"a":1,"ETSProp":"ets"}' + } + + It 'Should include ETS properties on OrderedDictionary via Pipeline and InputObject' { + $ordered = [ordered]@{ a = 1 } + $ordered = Add-Member -InputObject $ordered -MemberType NoteProperty -Name ETSProp -Value 'ets' -PassThru + $jsonPipeline = $ordered | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $ordered -Compress + $jsonPipeline | Should -BeExactly '{"a":1,"ETSProp":"ets"}' + $jsonInputObject | Should -BeExactly '{"a":1,"ETSProp":"ets"}' + } + } + + Context 'Generic Dictionary types' { + It 'Should serialize Generic Dictionary correctly via Pipeline and InputObject' { + $dict = [System.Collections.Generic.Dictionary[string,int]]::new() + $dict['one'] = 1 + $dict['two'] = 2 + $jsonPipeline = $dict | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $dict -Compress + $jsonPipeline | Should -Match '"one":1' + $jsonPipeline | Should -Match '"two":2' + $jsonInputObject | Should -Match '"one":1' + $jsonInputObject | Should -Match '"two":2' + } + + It 'Should serialize SortedDictionary correctly via Pipeline and InputObject' { + $dict = [System.Collections.Generic.SortedDictionary[string,int]]::new() + $dict['b'] = 2 + $dict['a'] = 1 + $dict['c'] = 3 + $jsonPipeline = $dict | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $dict -Compress + $jsonPipeline | Should -BeExactly '{"a":1,"b":2,"c":3}' + $jsonInputObject | Should -BeExactly '{"a":1,"b":2,"c":3}' + } + } + + #endregion Comprehensive Array and Dictionary Tests (Phase 2) + + + #region Comprehensive PSCustomObject Tests (Phase 3) + # Test coverage for ConvertTo-Json PSCustomObject serialization + # Covers: Pipeline vs InputObject, ETS vs no ETS, nested structures + + Context 'PSCustomObject basic serialization' { + It 'Should serialize PSCustomObject with single property via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ Name = 'Test' } + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly '{"Name":"Test"}' + $jsonInputObject | Should -BeExactly '{"Name":"Test"}' + } + + It 'Should serialize PSCustomObject with multiple properties via Pipeline and InputObject' { + $obj = [PSCustomObject][ordered]@{ + Name = 'Test' + Value = 42 + Active = $true + } + $expected = '{"Name":"Test","Value":42,"Active":true}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should preserve property order in PSCustomObject via Pipeline and InputObject' { + $obj = [PSCustomObject][ordered]@{ + Zebra = 1 + Alpha = 2 + Middle = 3 + } + $expected = '{"Zebra":1,"Alpha":2,"Middle":3}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize PSCustomObject with null property via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ NullProp = $null } + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly '{"NullProp":null}' + $jsonInputObject | Should -BeExactly '{"NullProp":null}' + } + } + + Context 'PSCustomObject with various property types' { + It 'Should serialize PSCustomObject with scalar properties via Pipeline and InputObject' { + $obj = [PSCustomObject][ordered]@{ + IntVal = 42 + DoubleVal = 3.14 + StringVal = 'hello' + BoolVal = $true + } + $expected = '{"IntVal":42,"DoubleVal":3.14,"StringVal":"hello","BoolVal":true}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize PSCustomObject with DateTime property via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + Date = [DateTime]::new(2024, 6, 15, 10, 30, 0, [DateTimeKind]::Utc) + } + $expected = '{"Date":"2024-06-15T10:30:00Z"}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize PSCustomObject with Guid property via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + Id = [Guid]'12345678-1234-1234-1234-123456789abc' + } + $expected = '{"Id":"12345678-1234-1234-1234-123456789abc"}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize PSCustomObject with enum property via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ Day = [DayOfWeek]::Monday } + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly '{"Day":1}' + $jsonInputObject | Should -BeExactly '{"Day":1}' + } + + It 'Should serialize PSCustomObject with enum as string via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ Day = [DayOfWeek]::Monday } + $jsonPipeline = $obj | ConvertTo-Json -Compress -EnumsAsStrings + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress -EnumsAsStrings + $jsonPipeline | Should -BeExactly '{"Day":"Monday"}' + $jsonInputObject | Should -BeExactly '{"Day":"Monday"}' + } + + It 'Should serialize PSCustomObject with array property via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ Numbers = @(1, 2, 3) } + $expected = '{"Numbers":[1,2,3]}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize PSCustomObject with hashtable property via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ Config = @{ Key = 'Value' } } + $expected = '{"Config":{"Key":"Value"}}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + } + + Context 'Nested PSCustomObject' { + It 'Should serialize nested PSCustomObject via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + Outer = [PSCustomObject]@{ + Inner = 'value' + } + } + $expected = '{"Outer":{"Inner":"value"}}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize deeply nested PSCustomObject via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + Level1 = [PSCustomObject]@{ + Level2 = [PSCustomObject]@{ + Level3 = 'deep' + } + } + } + $expected = '{"Level1":{"Level2":{"Level3":"deep"}}}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize nested PSCustomObject with Depth limit via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + Level1 = [PSCustomObject]@{ + Level2 = [PSCustomObject]@{ + Level3 = 'deep' + } + } + } + $expected = '{"Level1":{"Level2":"@{Level3=deep}"}}' + $jsonPipeline = $obj | ConvertTo-Json -Compress -Depth 1 + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress -Depth 1 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize PSCustomObject with mixed nested types via Pipeline and InputObject' { + $obj = [PSCustomObject][ordered]@{ + Child = [PSCustomObject]@{ Name = 'child' } + Items = @(1, 2, 3) + Config = @{ Key = 'Value' } + } + $expected = '{"Child":{"Name":"child"},"Items":[1,2,3],"Config":{"Key":"Value"}}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + } + + Context 'PSCustomObject ETS properties' { + It 'Should include NoteProperty on PSCustomObject via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ Original = 'value' } + $obj | Add-Member -MemberType NoteProperty -Name Added -Value 'added' + $expected = '{"Original":"value","Added":"added"}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should include ScriptProperty on PSCustomObject via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ Value = 10 } + $obj | Add-Member -MemberType ScriptProperty -Name Doubled -Value { $this.Value * 2 } + $expected = '{"Value":10,"Doubled":20}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should include multiple ETS properties on PSCustomObject via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ Base = 'base' } + $obj | Add-Member -MemberType NoteProperty -Name Note1 -Value 'note1' + $obj | Add-Member -MemberType NoteProperty -Name Note2 -Value 'note2' + $expected = '{"Base":"base","Note1":"note1","Note2":"note2"}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + } + + Context 'Array of PSCustomObject' { + It 'Should serialize array of PSCustomObject via Pipeline and InputObject' { + $arr = @( + [PSCustomObject][ordered]@{ Id = 1; Name = 'First' } + [PSCustomObject][ordered]@{ Id = 2; Name = 'Second' } + ) + $expected = '[{"Id":1,"Name":"First"},{"Id":2,"Name":"Second"}]' + $jsonPipeline = $arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize single PSCustomObject without array wrapper via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ Id = 1 } + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly '{"Id":1}' + $jsonInputObject | Should -BeExactly '{"Id":1}' + } + + It 'Should serialize single PSCustomObject with -AsArray via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ Id = 1 } + $jsonPipeline = $obj | ConvertTo-Json -Compress -AsArray + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress -AsArray + $jsonPipeline | Should -BeExactly '[{"Id":1}]' + $jsonInputObject | Should -BeExactly '[{"Id":1}]' + } + } + + #endregion Comprehensive PSCustomObject Tests (Phase 3) + + #region Comprehensive Depth Truncation and Multilevel Composition Tests (Phase 4) + # Test coverage for ConvertTo-Json depth truncation and complex nested structures + # Covers: -Depth parameter behavior, multilevel type compositions + + Context 'Depth parameter basic behavior' { + It 'Should use default depth of 2 via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + L0 = [PSCustomObject]@{ + L1 = [PSCustomObject]@{ + L2 = [PSCustomObject]@{ + L3 = 'deep' + } + } + } + } + $expected = '{"L0":{"L1":{"L2":"@{L3=deep}"}}}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should truncate at Depth 0 via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + L0 = [PSCustomObject]@{ L1 = 1 } + } + $expected = '{"L0":"@{L1=1}"}' + $jsonPipeline = $obj | ConvertTo-Json -Compress -Depth 0 + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress -Depth 0 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should truncate at Depth 1 via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + L0 = [PSCustomObject]@{ + L1 = [PSCustomObject]@{ + L2 = 'deep' + } + } + } + $expected = '{"L0":{"L1":"@{L2=deep}"}}' + $jsonPipeline = $obj | ConvertTo-Json -Compress -Depth 1 + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress -Depth 1 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize fully with sufficient Depth via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + L0 = [PSCustomObject]@{ + L1 = [PSCustomObject]@{ + L2 = [PSCustomObject]@{ + L3 = 'very deep' + } + } + } + } + $expected = '{"L0":{"L1":{"L2":{"L3":"very deep"}}}}' + $jsonPipeline = $obj | ConvertTo-Json -Compress -Depth 10 + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress -Depth 10 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should handle Depth 100 for deeply nested structures via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ L0 = [PSCustomObject]@{ L1 = [PSCustomObject]@{ L2 = [PSCustomObject]@{ L3 = [PSCustomObject]@{ L4 = 'deep' } } } } } + $expected = '{"L0":{"L1":{"L2":{"L3":{"L4":"deep"}}}}}' + $jsonPipeline = $obj | ConvertTo-Json -Compress -Depth 100 + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress -Depth 100 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should throw on Depth 101 exceeding maximum via Pipeline and InputObject' { + { [PSCustomObject]@{ L0 = 1 } | ConvertTo-Json -Depth 101 } | Should -Throw -ErrorId 'ParameterArgumentValidationError,Microsoft.PowerShell.Commands.ConvertToJsonCommand' + { ConvertTo-Json -InputObject ([PSCustomObject]@{ L0 = 1 }) -Depth 101 } | Should -Throw -ErrorId 'ParameterArgumentValidationError,Microsoft.PowerShell.Commands.ConvertToJsonCommand' + } + } + + Context 'Depth truncation with arrays' { + It 'Should truncate nested array at Depth limit via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + Arr = ,(,(1, 2, 3)) + } + $expected = '{"Arr":["System.Object[]"]}' + $jsonPipeline = $obj | ConvertTo-Json -Compress -Depth 1 + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress -Depth 1 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize nested array fully with sufficient Depth via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + Arr = ,(,(1, 2, 3)) + } + $expected = '{"Arr":[[[1,2,3]]]}' + $jsonPipeline = $obj | ConvertTo-Json -Compress -Depth 10 + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress -Depth 10 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should truncate array of objects at Depth limit via Pipeline and InputObject' { + $arr = @( + [PSCustomObject]@{ Inner = [PSCustomObject]@{ Value = 1 } } + ) + $expected = '[{"Inner":"@{Value=1}"}]' + $jsonPipeline = ,$arr | ConvertTo-Json -Compress -Depth 1 + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress -Depth 1 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + } + + Context 'Depth truncation with hashtables' { + It 'Should truncate nested hashtable at Depth limit via Pipeline and InputObject' { + $hash = @{ + L0 = @{ + L1 = @{ + L2 = 'deep' + } + } + } + $expected = '{"L0":{"L1":"System.Collections.Hashtable"}}' + $jsonPipeline = $hash | ConvertTo-Json -Compress -Depth 1 + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress -Depth 1 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize nested hashtable fully with sufficient Depth via Pipeline and InputObject' { + $hash = @{ + L0 = @{ + L1 = @{ + L2 = 'deep' + } + } + } + $expected = '{"L0":{"L1":{"L2":"deep"}}}' + $jsonPipeline = $hash | ConvertTo-Json -Compress -Depth 10 + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress -Depth 10 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + } + + Context 'Depth truncation string representation' { + It 'Should convert PSCustomObject to @{...} string when truncated via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + Child = [PSCustomObject]@{ A = 1; B = 2 } + } + $expected = '{"Child":"@{A=1; B=2}"}' + $jsonPipeline = $obj | ConvertTo-Json -Compress -Depth 0 + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress -Depth 0 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should convert Hashtable to type name when truncated via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + Child = @{ Key = 'Value' } + } + $expected = '{"Child":"System.Collections.Hashtable"}' + $jsonPipeline = $obj | ConvertTo-Json -Compress -Depth 0 + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress -Depth 0 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should convert Array to space-separated string when truncated via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + Child = @(1, 2, 3) + } + $expected = '{"Child":"1 2 3"}' + $jsonPipeline = $obj | ConvertTo-Json -Compress -Depth 0 + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress -Depth 0 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + } + + Context 'Multilevel composition: Array containing Dictionary' { + It 'Should serialize array of hashtables correctly via Pipeline and InputObject' { + $arr = @(@{ a = 1 }, @{ b = 2 }, @{ c = 3 }) + $expected = '[{"a":1},{"b":2},{"c":3}]' + $jsonPipeline = $arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize array of ordered dictionaries correctly via Pipeline and InputObject' { + $arr = @( + [ordered]@{ x = 1; y = 2 }, + [ordered]@{ x = 3; y = 4 } + ) + $expected = '[{"x":1,"y":2},{"x":3,"y":4}]' + $jsonPipeline = ,$arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize nested array of hashtables correctly via Pipeline and InputObject' { + $arr = @( + @{ + Items = @( + @{ Value = 1 }, + @{ Value = 2 } + ) + } + ) + $expected = '[{"Items":[{"Value":1},{"Value":2}]}]' + $jsonPipeline = ,$arr | ConvertTo-Json -Compress -Depth 3 + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress -Depth 3 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + } + + Context 'Multilevel composition: Dictionary containing Array' { + It 'Should serialize dictionary with array values correctly via Pipeline and InputObject' { + $hash = [ordered]@{ + numbers = @(1, 2, 3) + strings = @('a', 'b', 'c') + } + $expected = '{"numbers":[1,2,3],"strings":["a","b","c"]}' + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize dictionary with nested array values correctly via Pipeline and InputObject' { + $hash = @{ + matrix = @(@(1, 2), @(3, 4)) + } + $expected = '{"matrix":[[1,2],[3,4]]}' + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize dictionary with empty array value correctly via Pipeline and InputObject' { + $hash = @{ empty = @() } + $expected = '{"empty":[]}' + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize dictionary with array of dictionaries correctly via Pipeline and InputObject' { + $hash = @{ + Items = @( + @{ X = 1 }, + @{ X = 2 } + ) + } + $expected = '{"Items":[{"X":1},{"X":2}]}' + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + } + + Context 'Multilevel composition: PSCustomObject with mixed types' { + It 'Should serialize PSCustomObject with array and hashtable properties via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + List = @(1, 2, 3) + Config = @{ Key = 'Value' } + Name = 'Test' + } + $expected = '{"List":[1,2,3],"Config":{"Key":"Value"},"Name":"Test"}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize PSCustomObject with nested PSCustomObject and array via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + Child = [PSCustomObject]@{ + Items = @(1, 2, 3) + } + } + $expected = '{"Child":{"Items":[1,2,3]}}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize array of PSCustomObject with mixed properties via Pipeline and InputObject' { + $arr = @( + [PSCustomObject]@{ Type = 'A'; Data = @(1, 2) }, + [PSCustomObject]@{ Type = 'B'; Data = @{ Key = 'Val' } } + ) + $expected = '[{"Type":"A","Data":[1,2]},{"Type":"B","Data":{"Key":"Val"}}]' + $jsonPipeline = $arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + } + + Context 'Multilevel composition: PowerShell class in complex structures' { + BeforeAll { + class ItemClass { + [int]$Id + [string]$Name + } + + class ContainerClass { + [string]$Type + [ItemClass]$Item + } + } + + It 'Should serialize array of PowerShell class correctly via Pipeline and InputObject' { + $arr = @( + [ItemClass]@{ Id = 1; Name = 'First' }, + [ItemClass]@{ Id = 2; Name = 'Second' } + ) + $expected = '[{"Id":1,"Name":"First"},{"Id":2,"Name":"Second"}]' + $jsonPipeline = $arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize hashtable containing PowerShell class correctly via Pipeline and InputObject' { + $item = [ItemClass]@{ Id = 1; Name = 'Test' } + $hash = @{ Item = $item } + $expected = '{"Item":{"Id":1,"Name":"Test"}}' + $jsonPipeline = $hash | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize nested PowerShell classes correctly via Pipeline and InputObject' { + $item = [ItemClass]@{ Id = 1; Name = 'Inner' } + $container = [ContainerClass]@{ Type = 'Outer'; Item = $item } + $expected = '{"Type":"Outer","Item":{"Id":1,"Name":"Inner"}}' + $jsonPipeline = $container | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $container -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize PSCustomObject containing PowerShell class correctly via Pipeline and InputObject' { + $item = [ItemClass]@{ Id = 1; Name = 'Test' } + $obj = [PSCustomObject]@{ + Label = 'Container' + Content = $item + } + $expected = '{"Label":"Container","Content":{"Id":1,"Name":"Test"}}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should truncate nested PowerShell class at Depth limit via Pipeline and InputObject' { + $item = [ItemClass]@{ Id = 1; Name = 'Test' } + $container = [ContainerClass]@{ Type = 'Outer'; Item = $item } + $itemString = $item.ToString() + $expected = "{`"Type`":`"Outer`",`"Item`":`"$itemString`"}" + $jsonPipeline = $container | ConvertTo-Json -Compress -Depth 0 + $jsonInputObject = ConvertTo-Json -InputObject $container -Compress -Depth 0 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + } + + Context 'Complex multilevel compositions' { + It 'Should serialize 3-level mixed composition correctly via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + Users = @( + [PSCustomObject]@{ + Name = 'Alice' + Roles = @('Admin', 'User') + }, + [PSCustomObject]@{ + Name = 'Bob' + Roles = @('User') + } + ) + } + $expected = '{"Users":[{"Name":"Alice","Roles":["Admin","User"]},{"Name":"Bob","Roles":["User"]}]}' + $jsonPipeline = $obj | ConvertTo-Json -Compress -Depth 3 + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress -Depth 3 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize dictionary with nested mixed types correctly via Pipeline and InputObject' { + $hash = [ordered]@{ + Meta = [PSCustomObject]@{ Version = '1.0' } + Data = @( + ([ordered]@{ Key = 'A'; Values = @(1, 2) }), + ([ordered]@{ Key = 'B'; Values = @(3, 4) }) + ) + } + $expected = '{"Meta":{"Version":"1.0"},"Data":[{"Key":"A","Values":[1,2]},{"Key":"B","Values":[3,4]}]}' + $jsonPipeline = $hash | ConvertTo-Json -Compress -Depth 3 + $jsonInputObject = ConvertTo-Json -InputObject $hash -Compress -Depth 3 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should handle deeply nested mixed types with sufficient Depth via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + L0 = @{ + L1 = [PSCustomObject]@{ + L2 = @( + [PSCustomObject]@{ L3 = 'deep' } + ) + } + } + } + $expected = '{"L0":{"L1":{"L2":[{"L3":"deep"}]}}}' + $jsonPipeline = $obj | ConvertTo-Json -Compress -Depth 10 + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress -Depth 10 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should truncate deeply nested mixed types at Depth limit via Pipeline and InputObject' { + $obj = [PSCustomObject]@{ + L0 = @{ + L1 = [PSCustomObject]@{ + L2 = @( + [PSCustomObject]@{ L3 = 'deep' } + ) + } + } + } + $expected = '{"L0":{"L1":{"L2":""}}}' + $jsonPipeline = $obj | ConvertTo-Json -Compress -Depth 2 + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress -Depth 2 + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + } + + #endregion Comprehensive Depth Truncation and Multilevel Composition Tests (Phase 4) + + #region Comprehensive PowerShell Class Tests (Phase 5) + # Test coverage for ConvertTo-Json PowerShell class serialization + # Covers: Pipeline vs InputObject, ETS vs no ETS, nested structures, inheritance + + Context 'PowerShell class serialization' { + BeforeAll { + class SimpleClass { + [string]$StringVal + [int]$IntVal + [bool]$BoolVal + [double]$DoubleVal + [bigint]$BigIntVal + [guid]$GuidVal + [ipaddress]$IPVal + [object[]]$ArrayVal + [System.Collections.Specialized.OrderedDictionary]$DictVal + hidden [string]$HiddenVal + } + } + + It 'Should serialize PowerShell class with various property types including ETS via Pipeline and InputObject' { + $obj = [SimpleClass]::new() + $obj.StringVal = 'hello' + $obj.IntVal = 42 + $obj.BoolVal = $true + $obj.DoubleVal = 3.14 + $obj.BigIntVal = [bigint]::Parse('99999999999999999999') + $obj.GuidVal = [guid]'12345678-1234-1234-1234-123456789abc' + $obj.IPVal = [ipaddress]::Parse('192.168.1.1') + $obj.ArrayVal = @(1, 'two', $true) + $obj.DictVal = [ordered]@{ Key = 'Value'; Nested = [ordered]@{ Inner = 1 } } + $obj.HiddenVal = 'secret' + $obj | Add-Member -MemberType NoteProperty -Name ETSNote -Value 'note' + $obj | Add-Member -MemberType ScriptProperty -Name ETSScript -Value { $this.StringVal.Length } + $obj.IPVal | Add-Member -MemberType NoteProperty -Name Label -Value 'primary' + $expectedPipeline = '{"StringVal":"hello","IntVal":42,"BoolVal":true,"DoubleVal":3.14,"BigIntVal":99999999999999999999,"GuidVal":"12345678-1234-1234-1234-123456789abc","IPVal":{"AddressFamily":2,"ScopeId":null,"IsIPv6Multicast":false,"IsIPv6LinkLocal":false,"IsIPv6SiteLocal":false,"IsIPv6Teredo":false,"IsIPv6UniqueLocal":false,"IsIPv4MappedToIPv6":false,"Address":16885952},"ArrayVal":[1,"two",true],"DictVal":{"Key":"Value","Nested":{"Inner":1}},"HiddenVal":"secret","ETSNote":"note","ETSScript":5}' + $expectedInputObject = '{"StringVal":"hello","IntVal":42,"BoolVal":true,"DoubleVal":3.14,"BigIntVal":99999999999999999999,"GuidVal":"12345678-1234-1234-1234-123456789abc","IPVal":{"AddressFamily":2,"ScopeId":null,"IsIPv6Multicast":false,"IsIPv6LinkLocal":false,"IsIPv6SiteLocal":false,"IsIPv6Teredo":false,"IsIPv6UniqueLocal":false,"IsIPv4MappedToIPv6":false,"Address":16885952},"ArrayVal":[1,"two",true],"DictVal":{"Key":"Value","Nested":{"Inner":1}},"HiddenVal":"secret"}' + $jsonPipeline = $obj | ConvertTo-Json -Compress -Depth 3 + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress -Depth 3 + $jsonPipeline | Should -BeExactly $expectedPipeline + $jsonInputObject | Should -BeExactly $expectedInputObject + } + + It 'Should serialize PowerShell class with default values via Pipeline and InputObject' { + $obj = [SimpleClass]::new() + $expected = '{"StringVal":null,"IntVal":0,"BoolVal":false,"DoubleVal":0.0,"BigIntVal":0,"GuidVal":"00000000-0000-0000-0000-000000000000","IPVal":null,"ArrayVal":null,"DictVal":null,"HiddenVal":null}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + } + + Context 'Nested PowerShell class' { + BeforeAll { + class InnerClass { + [string]$Inner + } + + class OuterClass { + [string]$Outer + [InnerClass]$Child + } + + class DeepClass { + [string]$Name + [OuterClass]$Nested + } + } + + It 'Should serialize deeply nested PowerShell class via Pipeline and InputObject' { + $inner = [InnerClass]@{ Inner = 'deep' } + $outer = [OuterClass]@{ Outer = 'middle'; Child = $inner } + $deep = [DeepClass]@{ Name = 'top'; Nested = $outer } + $expected = '{"Name":"top","Nested":{"Outer":"middle","Child":{"Inner":"deep"}}}' + $jsonPipeline = $deep | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $deep -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize nested PowerShell class with null child via Pipeline and InputObject' { + $outer = [OuterClass]@{ Outer = 'outer value'; Child = $null } + $expected = '{"Outer":"outer value","Child":null}' + $jsonPipeline = $outer | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $outer -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + } + + Context 'PowerShell class inheritance' { + BeforeAll { + class BaseClass { + [string]$BaseProp + } + + class ChildClass : BaseClass { + [string]$ChildProp + } + + class GrandChildClass : ChildClass { + [string]$GrandChildProp + } + } + + It 'Should serialize derived class with base properties via Pipeline and InputObject' { + $obj = [ChildClass]@{ BaseProp = 'base'; ChildProp = 'child' } + $expected = '{"ChildProp":"child","BaseProp":"base"}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + It 'Should serialize multi-level inherited class via Pipeline and InputObject' { + $obj = [GrandChildClass]@{ + BaseProp = 'base' + ChildProp = 'child' + GrandChildProp = 'grandchild' + } + $expected = '{"GrandChildProp":"grandchild","ChildProp":"child","BaseProp":"base"}' + $jsonPipeline = $obj | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $obj -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + + } + + Context 'Mixed PSCustomObject and PowerShell class' { + BeforeAll { + class MixedClass { + [string]$ClassName + } + } + + It 'Should serialize array with mixed types via Pipeline and InputObject' { + $classObj = [MixedClass]@{ ClassName = 'class' } + $customObj = [PSCustomObject]@{ CustomName = 'custom' } + $arr = @($classObj, $customObj) + $expected = '[{"ClassName":"class"},{"CustomName":"custom"}]' + $jsonPipeline = $arr | ConvertTo-Json -Compress + $jsonInputObject = ConvertTo-Json -InputObject $arr -Compress + $jsonPipeline | Should -BeExactly $expected + $jsonInputObject | Should -BeExactly $expected + } + } + + #endregion Comprehensive PowerShell Class Tests (Phase 5) + } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-SecureString.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-SecureString.Tests.ps1 index e58a227fbcf..5a2660ed621 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-SecureString.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-SecureString.Tests.ps1 @@ -1,5 +1,9 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. + +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] +param() + Describe "ConvertTo--SecureString" -Tags "CI" { Context "Checking return types of ConvertTo--SecureString" { diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Debug-Runspace.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Debug-Runspace.Tests.ps1 index 8aa28b00aef..2b517e4f301 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Debug-Runspace.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Debug-Runspace.Tests.ps1 @@ -31,6 +31,33 @@ Describe "Debug-Runspace" -Tag "CI" { $rs1.Debugger.SetDebugMode("None") { Debug-Runspace -Runspace $rs1 -ErrorAction stop } | Should -Throw -ErrorId "InvalidOperation,Microsoft.PowerShell.Commands.DebugRunspaceCommand" } + + It "Should write attach event and mark runspace as having a remote debugger attached" { + $onAttachName = [System.Management.Automation.PSEngineEvent]::OnDebugAttach + + $debugTarget = [PowerShell]::Create() + $null = $debugTarget.AddCommand('Wait-Event').AddParameter('SourceIdentifier', $onAttachName) + $waitTask = $debugTarget.BeginInvoke() + $debugTarget.Runspace.IsRemoteDebuggerAttached | Should -BeFalse + + $debugger = [PowerShell]::Create() + $null = $debugger.AddCommand('Debug-Runspace').AddParameter('Id', $debugTarget.Runspace.Id) + $debugTask = $debugger.BeginInvoke() + + $waitTask.AsyncWaitHandle.WaitOne(5000) | Should -BeTrue + $waitInfo = $debugTarget.EndInvoke($waitTask) + $waitInfo.SourceIdentifier | Should -Be $onAttachName + + $debugTarget.Runspace.IsRemoteDebuggerAttached | Should -BeTrue + + $debugger.Stop() + $exp = { + $debugger.EndInvoke($debugTask) + } | Should -Throw -PassThru + $exp.FullyQualifiedErrorId | Should -Be "PipelineStoppedException" + + $debugTarget.Runspace.IsRemoteDebuggerAttached | Should -BeFalse + } } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-Csv.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-Csv.Tests.ps1 index 67234f24c58..aae457f2f82 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-Csv.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-Csv.Tests.ps1 @@ -11,6 +11,7 @@ Describe "Export-Csv" -Tags "CI" { $P1 = [pscustomobject]@{"P1" = "first"} $P2 = [pscustomobject]@{"P2" = "second"} $P11 = [pscustomobject]@{"P1" = "eleventh"} + $testHashTable = @{ 'first' = "value1"; 'second' = $null; 'third' = "value3" } } AfterEach { @@ -90,11 +91,6 @@ Describe "Export-Csv" -Tags "CI" { $results[0] | Should -BeExactly "#TYPE System.String" } - It "Does not support -IncludeTypeInformation and -NoTypeInformation at the same time" { - { $testObject | Export-Csv -Path $testCsv -IncludeTypeInformation -NoTypeInformation } | - Should -Throw -ErrorId "CannotSpecifyIncludeTypeInformationAndNoTypeInformation,Microsoft.PowerShell.Commands.ExportCsvCommand" - } - It "Should support -LiteralPath parameter" { $testObject | Export-Csv -LiteralPath $testCsv $results = Import-Csv -Path $testCsv @@ -186,6 +182,10 @@ Describe "Export-Csv" -Tags "CI" { $results[1].PSObject.properties.Name | Should -Not -Contain 'third' } + It "Should throw when -Append and -NoHeader are specified together" { + { $P1 | Export-Csv -Path $testCsv -Append -NoHeader -ErrorAction Stop } | Should -Throw -ErrorId "CannotSpecifyBothAppendAndNoHeader,Microsoft.PowerShell.Commands.ExportCsvCommand" + } + It "First line should be #TYPE if -IncludeTypeInformation used and pstypenames object property is empty" { $object = [PSCustomObject]@{first = 1} $pstypenames = $object.pstypenames | ForEach-Object -Process {$_} @@ -249,6 +249,33 @@ Describe "Export-Csv" -Tags "CI" { $contents[1].Contains($delimiter) | Should -BeTrue } + It "Should not throw when exporting hashtable with property that has null value"{ + { $testHashTable | Export-Csv -Path $testCsv } | Should -Not -Throw + } + + It "Should not throw when exporting PSCustomObject with property that has null value"{ + $testObject = [pscustomobject]$testHashTable + { $testObject | Export-Csv -Path $testCsv } | Should -Not -Throw + } + + It "Export hashtable with null and non-null values"{ + $testHashTable | Export-Csv -Path $testCsv + $result2 = Import-CSV -Path $testCsv + + $result2.first | Should -BeExactly "value1" + $result2.second | Should -BeNullOrEmpty + $result2.third | Should -BeExactly "value3" + } + + It "Export hashtable with non-null values"{ + $testTable = @{ 'first' = "value1"; 'second' = "value2" } + $testTable | Export-Csv -Path $testCsv + $results = Import-CSV -Path $testCsv + + $results.first | Should -BeExactly "value1" + $results.second | Should -BeExactly "value2" + } + Context "UseQuotes parameter" { # A minimum of tests. The rest are in ConvertTo-Csv.Tests.ps1 diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-FormatData.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-FormatData.Tests.ps1 index c024f364c68..3325b21a9c4 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-FormatData.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Export-FormatData.Tests.ps1 @@ -16,7 +16,7 @@ Describe "Export-FormatData" -Tags "CI" { { $fd | Export-FormatData -Path $TESTDRIVE\allformat.ps1xml -IncludeScriptBlock - $sessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault() + $sessionState = [initialsessionstate]::CreateDefault() $sessionState.Formats.Clear() $sessionState.Types.Clear() @@ -135,7 +135,7 @@ Describe "Export-FormatData" -Tags "CI" { $testfile = Join-Path -Path $TestDrive -ChildPath "$testfilename.ps1xml" Set-Content -Path $testfile -Value $xmlContent - $sessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault() + $sessionState = [initialsessionstate]::CreateDefault() $sessionState.Formats.Clear() $sessionState.Types.Clear() diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-Custom.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-Custom.Tests.ps1 index 80aad6ef5b9..c82d8b77b8a 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-Custom.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-Custom.Tests.ps1 @@ -466,4 +466,48 @@ SelectScriptBlock $ps.Invoke() -replace '\r?\n', "^" | Should -BeExactly $expectedOutput $ps.Streams.Error | Should -BeNullOrEmpty } + + Context 'ExcludeProperty parameter' { + It 'Should exclude specified properties' { + $obj = [pscustomobject]@{ Name = 'Test'; Age = 30; City = 'Seattle' } + $result = $obj | Format-Custom -ExcludeProperty Age | Out-String + $result | Should -Match 'Name' + $result | Should -Match 'City' + $result | Should -Not -Match 'Age' + } + + It 'Should work with wildcard patterns' { + $obj = [pscustomobject]@{ Prop1 = 1; Prop2 = 2; Other = 3 } + $result = $obj | Format-Custom -ExcludeProperty Prop* | Out-String + $result | Should -Match 'Other' + $result | Should -Not -Match 'Prop1' + $result | Should -Not -Match 'Prop2' + } + + It 'Should work without Property parameter (implies -Property *)' { + $obj = [pscustomobject]@{ A = 1; B = 2; C = 3 } + $result = $obj | Format-Custom -ExcludeProperty B | Out-String + $result | Should -Match 'A\s*=' + $result | Should -Match 'C\s*=' + $result | Should -Not -Match 'B\s*=' + } + + It 'Should work with Property parameter' { + $obj = [pscustomobject]@{ Name = 'Test'; Age = 30; City = 'Seattle'; Country = 'USA' } + $result = $obj | Format-Custom -Property Name, Age, City, Country -ExcludeProperty Age | Out-String + $result | Should -Match 'Name' + $result | Should -Match 'City' + $result | Should -Match 'Country' + $result | Should -Not -Match 'Age' + } + + It 'Should handle multiple excluded properties' { + $obj = [pscustomobject]@{ A = 1; B = 2; C = 3; D = 4 } + $result = $obj | Format-Custom -ExcludeProperty B, D | Out-String + $result | Should -Match 'A\s*=' + $result | Should -Match 'C\s*=' + $result | Should -Not -Match 'B\s*=' + $result | Should -Not -Match 'D\s*=' + } + } } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-List.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-List.Tests.ps1 index b9079b92139..342383f3880 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-List.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-List.Tests.ps1 @@ -261,4 +261,48 @@ Describe 'Format-List color tests' -Tag 'CI' { $output = Get-Content "$TestDrive/outfile.txt" -Raw $output.Trim().Replace("`r", "") | Should -BeExactly $expected.Replace("`r", "") } + + Context 'ExcludeProperty parameter' { + It 'Should exclude specified properties' { + $obj = [pscustomobject]@{ Name = 'Test'; Age = 30; City = 'Seattle' } + $result = $obj | Format-List -ExcludeProperty Age | Out-String + $result | Should -Match 'Name' + $result | Should -Match 'City' + $result | Should -Not -Match 'Age' + } + + It 'Should work with wildcard patterns' { + $obj = [pscustomobject]@{ Prop1 = 1; Prop2 = 2; Other = 3 } + $result = $obj | Format-List -ExcludeProperty Prop* | Out-String + $result | Should -Match 'Other' + $result | Should -Not -Match 'Prop1' + $result | Should -Not -Match 'Prop2' + } + + It 'Should work without Property parameter (implies -Property *)' { + $obj = [pscustomobject]@{ A = 1; B = 2; C = 3 } + $result = $obj | Format-List -ExcludeProperty B | Out-String + $result | Should -Match 'A' + $result | Should -Match 'C' + $result | Should -Not -Match 'B' + } + + It 'Should work with Property parameter' { + $obj = [pscustomobject]@{ Name = 'Test'; Age = 30; City = 'Seattle'; Country = 'USA' } + $result = $obj | Format-List -Property Name, Age, City, Country -ExcludeProperty Age | Out-String + $result | Should -Match 'Name' + $result | Should -Match 'City' + $result | Should -Match 'Country' + $result | Should -Not -Match 'Age' + } + + It 'Should handle multiple excluded properties' { + $obj = [pscustomobject]@{ A = 1; B = 2; C = 3; D = 4 } + $result = $obj | Format-List -ExcludeProperty B, D | Out-String + $result | Should -Match 'A' + $result | Should -Match 'C' + $result | Should -Not -Match 'B' + $result | Should -Not -Match 'D' + } + } } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-Table.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-Table.Tests.ps1 index 34f2245909c..d102ef8bbdf 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-Table.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-Table.Tests.ps1 @@ -953,4 +953,48 @@ Describe 'Table color tests' -Tag 'CI' { $actual | Should -BeExactly $expected } + + Context 'ExcludeProperty parameter' { + It 'Should exclude specified properties' { + $obj = [pscustomobject]@{ Name = 'Test'; Age = 30; City = 'Seattle' } + $result = $obj | Format-Table -ExcludeProperty Age | Out-String + $result | Should -Match 'Name' + $result | Should -Match 'City' + $result | Should -Not -Match 'Age' + } + + It 'Should work with wildcard patterns' { + $obj = [pscustomobject]@{ Prop1 = 1; Prop2 = 2; Other = 3 } + $result = $obj | Format-Table -ExcludeProperty Prop* | Out-String + $result | Should -Match 'Other' + $result | Should -Not -Match 'Prop1' + $result | Should -Not -Match 'Prop2' + } + + It 'Should work without Property parameter (implies -Property *)' { + $obj = [pscustomobject]@{ A = 1; B = 2; C = 3 } + $result = $obj | Format-Table -ExcludeProperty B | Out-String + $result | Should -Match 'A' + $result | Should -Match 'C' + $result | Should -Not -Match 'B' + } + + It 'Should work with Property parameter' { + $obj = [pscustomobject]@{ Name = 'Test'; Age = 30; City = 'Seattle'; Country = 'USA' } + $result = $obj | Format-Table -Property Name, Age, City, Country -ExcludeProperty Age | Out-String + $result | Should -Match 'Name' + $result | Should -Match 'City' + $result | Should -Match 'Country' + $result | Should -Not -Match 'Age' + } + + It 'Should handle multiple excluded properties' { + $obj = [pscustomobject]@{ A = 1; B = 2; C = 3; D = 4 } + $result = $obj | Format-Table -ExcludeProperty B, D | Out-String + $result | Should -Match 'A' + $result | Should -Match 'C' + $result | Should -Not -Match 'B' + $result | Should -Not -Match 'D' + } + } } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-Wide.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-Wide.Tests.ps1 index c81db9fa1f9..10acd699068 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-Wide.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Format-Wide.Tests.ps1 @@ -105,4 +105,56 @@ Describe "Format-Wide DRT basic functionality" -Tags "CI" { $result | Should -Match " GroupingKey:" $result | Should -Match "name2\s+name3" } + + Context 'ExcludeProperty parameter' { + It 'Should exclude specified property and display first remaining' { + # PSCustomObject properties are in definition order: Name, Age, City + $obj = [pscustomobject]@{ Name = 'Test'; Age = 30; City = 'Seattle' } + # Exclude Name, should display Age (first remaining) + $result = $obj | Format-Wide -ExcludeProperty Name | Out-String + $result | Should -Match '30' + $result | Should -Not -Match 'Test' + } + + It 'Should work with wildcard patterns' { + # Properties: Prop1, Prop2, Other + $obj = [pscustomobject]@{ Prop1 = 1; Prop2 = 2; Other = 3 } + # Exclude Prop*, should display Other (only remaining) + $result = $obj | Format-Wide -ExcludeProperty Prop* | Out-String + $result | Should -Match '3' + $result | Should -Not -Match '1' + $result | Should -Not -Match '2' + } + + It 'Should work without Property parameter (implies -Property *)' { + # Properties: A, B, C + $obj = [pscustomobject]@{ A = 1; B = 2; C = 3 } + # Exclude B, C - should display A (first remaining) + $result = $obj | Format-Wide -ExcludeProperty B, C | Out-String + $result | Should -Match '1' + $result | Should -Not -Match '2' + $result | Should -Not -Match '3' + } + + It 'Should display first remaining property after exclusion' { + # Properties: Name, Age, City, Country + $obj = [pscustomobject]@{ Name = 'Test'; Age = 30; City = 'Seattle'; Country = 'USA' } + # Exclude Name and Age, should display City (first remaining) + $result = $obj | Format-Wide -ExcludeProperty Name, Age | Out-String + $result | Should -Match 'Seattle' + $result | Should -Not -Match 'Test' + $result | Should -Not -Match '30' + # USA might appear but not in the wide field, or might not appear at all since only first property is shown + } + + It 'Should handle multiple excluded properties' { + # Properties: A, B, C, D + $obj = [pscustomobject]@{ A = 1; B = 2; C = 3; D = 4 } + # Exclude A and B, should display C (first remaining) + $result = $obj | Format-Wide -ExcludeProperty A, B | Out-String + $result | Should -Match '3' + $result | Should -Not -Match '1' + $result | Should -Not -Match '2' + } + } } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Alias.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Alias.Tests.ps1 index edccb912ca7..a5b0b039d00 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Alias.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Alias.Tests.ps1 @@ -155,6 +155,16 @@ Describe "Get-Alias DRT Unit Tests" -Tags "CI" { $returnObject[$i].Definition | Should -Be 'Get-Command' } } + + It "Get-Alias DisplayName should always show AliasName -> ResolvedCommand for all aliases" { + Set-Alias -Name Test-MyAlias -Value Get-Command -Force + Set-Alias -Name tma -Value Test-MyAlias -force + $aliases = Get-Alias Test-MyAlias, tma + $aliases | ForEach-Object { + $_.DisplayName | Should -Be "$($_.Name) -> Get-Command" + } + $aliases.Name.foreach{Remove-Item Alias:$_ -ErrorAction SilentlyContinue} + } } Describe "Get-Alias" -Tags "CI" { diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Random.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Random.Tests.ps1 index cd9fee41873..100ca90b357 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Random.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Random.Tests.ps1 @@ -162,6 +162,12 @@ Describe "Get-Random" -Tags "CI" { $randomNumber | Should -BeIn 1, 2, 3, 5, 8, 13 } + It "Should return a single random item when -Shuffle:`$false is used" { + $randomNumber = Get-Random -InputObject 1, 2, 3, 5, 8, 13 -Shuffle:$false + $randomNumber.Count | Should -Be 1 + $randomNumber | Should -BeIn 1, 2, 3, 5, 8, 13 + } + It "Should return for a string collection " { $randomNumber = Get-Random -InputObject "red", "yellow", "blue" $randomNumber | Should -Be ("red" -or "yellow" -or "blue") diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-SecureRandom.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-SecureRandom.Tests.ps1 index acc560abdb3..957bfa30e9e 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-SecureRandom.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-SecureRandom.Tests.ps1 @@ -150,6 +150,12 @@ Describe "Get-SecureRandom" -Tags "CI" { $randomNumber | Should -BeIn 1, 2, 3, 5, 8, 13 } + It "Should return a single random item when -Shuffle:`$false is used" { + $randomNumber = Get-SecureRandom -InputObject 1, 2, 3, 5, 8, 13 -Shuffle:$false + $randomNumber.Count | Should -Be 1 + $randomNumber | Should -BeIn 1, 2, 3, 5, 8, 13 + } + It "Should return for a string collection " { $randomNumber = Get-SecureRandom -InputObject "red", "yellow", "blue" $randomNumber | Should -Be ("red" -or "yellow" -or "blue") diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Uptime.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Uptime.Tests.ps1 index aff5e4d50af..b3275a61f2a 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Uptime.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-Uptime.Tests.ps1 @@ -25,6 +25,10 @@ Describe "Get-Uptime" -Tags "CI" { $upt = Get-Uptime -Since $upt | Should -BeOfType DateTime } + It "Get-Uptime -Since:`$false return TimeSpan" { + $upt = Get-Uptime -Since:$false + $upt | Should -BeOfType TimeSpan + } It "Get-Uptime throw if IsHighResolution == false" { # Enable the test hook [system.management.automation.internal.internaltesthooks]::SetTestHook('StopwatchIsNotHighResolution', $true) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Implicit.Remoting.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Implicit.Remoting.Tests.ps1 index b532d7523ef..0c15fe6d359 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Implicit.Remoting.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Implicit.Remoting.Tests.ps1 @@ -1901,7 +1901,7 @@ try Export-PSSession -Session $session -OutputModule $tempdir\Diag -CommandName New-Guid -AllowClobber > $null # Only the snapin Microsoft.PowerShell.Core is loaded - $iss = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault2() + $iss = [initialsessionstate]::CreateDefault2() $ps = [PowerShell]::Create($iss) $result = $ps.AddScript(" & $tempdir\TestBug450687.ps1").Invoke() diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/New-Guid.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/New-Guid.Tests.ps1 index 54d609d2753..8756fed7726 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/New-Guid.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/New-Guid.Tests.ps1 @@ -17,6 +17,11 @@ Describe "New-Guid" -Tags "CI" { $guid.ToString() | Should -BeExactly "00000000-0000-0000-0000-000000000000" } + It "Should respect explicit false value for -Empty" { + $guid = New-Guid -Empty:$false + $guid.ToString() | Should -Not -BeExactly "00000000-0000-0000-0000-000000000000" + } + It "Should convert a string to a guid" { $guid1 = New-Guid $guid2 = New-Guid -InputObject $guid1.ToString() diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/PowerShellData.tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/PowerShellData.tests.ps1 index cfba4a4cbed..148993b69b1 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/PowerShellData.tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/PowerShellData.tests.ps1 @@ -49,4 +49,10 @@ Describe "Tests for the Import-PowerShellDataFile cmdlet" -Tags "CI" { $result = Import-PowerShellDataFile $largePsd1Path -SkipLimitCheck $result.Keys.Count | Should -Be 501 } + + It 'Fails if psd1 file is insecure while -SkipLimitCheck is used' { + $path = Setup -f insecure2.psd1 -Content '@{ Foo = [object] (calc.exe) }' -pass + { Import-PowerShellDataFile $path -SkipLimitCheck -ErrorAction Stop } | + Should -Throw -ErrorId "System.InvalidOperationException,Microsoft.PowerShell.Commands.ImportPowerShellDataFileCommand" + } } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Remove-TypeData.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Remove-TypeData.Tests.ps1 index ba009de4b1b..96178fa13c0 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Remove-TypeData.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Remove-TypeData.Tests.ps1 @@ -36,7 +36,7 @@ Describe "Remove-TypeData DRT Unit Tests" -Tags "CI" { BeforeEach { $ps = [powershell]::Create() - $iss = [system.management.automation.runspaces.initialsessionstate]::CreateDefault2() + $iss = [initialsessionstate]::CreateDefault2() $rs = [system.management.automation.runspaces.runspacefactory]::CreateRunspace($iss) $rs.Open() $ps.Runspace = $rs diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Set-Variable.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Set-Variable.Tests.ps1 index d11fd5cfbb3..6e6fc1a2e1d 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Set-Variable.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Set-Variable.Tests.ps1 @@ -244,44 +244,30 @@ Describe "Set-Variable" -Tags "CI" { Context "Set-Variable -Append tests" { BeforeAll { - if (! (Get-ExperimentalFeature PSRedirectToVariable).Enabled) { - $skipTest = $true - } - - $testCases = @{ value = 2; Count = 2 }, - @{ value = @(2,3,4); Count = 2}, - @{ value = "abc",(Get-Process -Id $PID) ; count = 2} + $testCases = @{ value = 2; Count = 2 }, + @{ value = @(2,3,4); Count = 2}, + @{ value = "abc",(Get-Process -Id $PID) ; count = 2} } - It "Can append values <value> to a variable" -testCases $testCases { - param ($value, $count) - - if ($skipTest) { - Set-ItResult -skip -because "Experimental Feature PSRedirectToVariable not enabled" - return - } - - $variableName = "testVar" - Set-Variable -Name $variableName -Value 1 - Set-Variable -Name $variableName -Value $value -Append + It "Can append values <value> to a variable" -testCases $testCases { + param ($value, $count) - $observedValues = Get-Variable $variableName -Value + $variableName = "testVar" + Set-Variable -Name $variableName -Value 1 + Set-Variable -Name $variableName -Value $value -Append - $observedValues.Count | Should -Be $count - $observedValues[0] | Should -Be 1 + $observedValues = Get-Variable $variableName -Value - $observedValues[1] | Should -Be $value - } + $observedValues.Count | Should -Be $count + $observedValues[0] | Should -Be 1 - It "Can use set-variable via streaming and append values" { - if ($skipTest) { - Set-ItResult -skip -because "Experimental Feature PSRedirectToVariable not enabled" - return - } + $observedValues[1] | Should -Be $value + } - $testVar = 1 - 4..6 | Set-Variable -Name testVar -Append - $testVar | Should -Be @(1,4,5,6) - } + It "Can use set-variable via streaming and append values" { + $testVar = 1 + 4..6 | Set-Variable -Name testVar -Append + $testVar | Should -Be @(1,4,5,6) + } } } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Test-Json.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Test-Json.Tests.ps1 index 32a6cf3ce63..7b1b58d8258 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Test-Json.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Test-Json.Tests.ps1 @@ -86,6 +86,141 @@ Describe "Test-Json" -Tags "CI" { } '@ + # Schema using oneOf to allow either integer or string pattern for port items + $oneOfSchema = @' + { + "type": "object", + "properties": { + "ports": { + "type": "array", + "items": { + "oneOf": [ + { "type": "integer", "minimum": 0, "maximum": 65535 }, + { "type": "string", "pattern": "^\\d+-\\d+$" } + ] + } + } + } + } +'@ + + # Valid JSON where ports are integers (first oneOf choice matches) + $validOneOfJson = '{ "ports": [80, 443, 8080] }' + + # Invalid JSON where a port value matches neither oneOf choice + $invalidOneOfJson = '{ "ports": [80, "invalid-port", 8080] }' + + # Schema using oneOf to allow either smartphone or laptop device types + $oneOfDeviceSchema = @' + { + "type": "object", + "properties": { + "Devices": { + "type": "array", + "items": { + "type": "object", + "oneOf": [ + { + "properties": { + "id": { "type": "string" }, + "deviceType": { "const": "smartphone" }, + "os": { "type": "string", "enum": ["iOS", "Android"] } + }, + "required": ["deviceType", "os"] + }, + { + "properties": { + "id": { "type": "string" }, + "deviceType": { "const": "laptop" }, + "arch": { "type": "string", "enum": ["x86", "x64", "arm64"] } + }, + "required": ["deviceType", "arch"] + } + ] + } + } + }, + "required": ["Devices"] + } +'@ + + # Valid JSON with mixed device types (all matching their respective oneOf choice) + $validOneOfDeviceJson = @' + { + "Devices": [ + { "id": "0", "deviceType": "laptop", "arch": "x64" }, + { "id": "1", "deviceType": "smartphone", "os": "iOS" }, + { "id": "2", "deviceType": "laptop", "arch": "arm64" }, + { "id": "3", "deviceType": "smartphone", "os": "Android" } + ] + } +'@ + + # Invalid JSON where only Devices/3 has an invalid os value + $invalidOneOfDeviceJson = @' + { + "Devices": [ + { "id": "0", "deviceType": "laptop", "arch": "x64" }, + { "id": "1", "deviceType": "smartphone", "os": "iOS" }, + { "id": "2", "deviceType": "laptop", "arch": "arm64" }, + { "id": "3", "deviceType": "smartphone", "os": "WindowsPhone" } + ] + } +'@ + + # Schema using anyOf to allow either smartphone or laptop device types + $anyOfDeviceSchema = @' + { + "type": "object", + "properties": { + "Devices": { + "type": "array", + "items": { + "type": "object", + "anyOf": [ + { + "properties": { + "deviceType": { "const": "smartphone" }, + "os": { "type": "string", "enum": ["iOS", "Android"] } + }, + "required": ["deviceType", "os"] + }, + { + "properties": { + "deviceType": { "const": "laptop" }, + "arch": { "type": "string", "enum": ["x86", "x64", "arm64"] } + }, + "required": ["deviceType", "arch"] + } + ] + } + } + }, + "required": ["Devices"] + } +'@ + + # Valid JSON with mixed device types (all matching their respective anyOf choice) + $validAnyOfDeviceJson = @' + { + "Devices": [ + { "deviceType": "laptop", "arch": "x64" }, + { "deviceType": "smartphone", "os": "iOS" } + ] + } +'@ + + # Invalid JSON where only Devices/2 has an invalid os value + $invalidAnyOfDeviceJson = @' + { + "Devices": [ + { "deviceType": "laptop", "arch": "x64" }, + { "deviceType": "smartphone", "os": "iOS" }, + { "deviceType": "smartphone", "os": "WindowsPhone" } + ] + } +'@ + $validJsonPath = Join-Path -Path $TestDrive -ChildPath 'validJson.json' $validLiteralJsonPath = Join-Path -Path $TestDrive -ChildPath "[valid]Json.json" $invalidNodeInJsonPath = Join-Path -Path $TestDrive -ChildPath 'invalidNodeInJson.json' @@ -343,4 +478,64 @@ Describe "Test-Json" -Tags "CI" { # With options should pass ($Json | Test-Json -Option $Options -ErrorAction SilentlyContinue) | Should -BeTrue } + + It "Test-Json does not report false positives for valid oneOf matches" { + $errorVar = $null + $result = Test-Json -Json $validOneOfJson -Schema $oneOfSchema -ErrorVariable errorVar -ErrorAction SilentlyContinue + + $result | Should -BeTrue + $errorVar.Count | Should -Be 0 + } + + It "Test-Json reports only relevant errors for invalid oneOf values" { + $errorVar = $null + $result = Test-Json -Json $invalidOneOfJson -Schema $oneOfSchema -ErrorVariable errorVar -ErrorAction SilentlyContinue + + $result | Should -BeFalse + # Should report error only for the invalid item, not for valid items + $errorVar.Count | Should -BeGreaterThan 0 + $errorVar[0].Exception.Message | Should -Match "/ports/1" + } + + It "Test-Json does not report false positives for valid oneOf device matches" { + $errorVar = $null + $result = Test-Json -Json $validOneOfDeviceJson -Schema $oneOfDeviceSchema -ErrorVariable errorVar -ErrorAction SilentlyContinue + + $result | Should -BeTrue + $errorVar.Count | Should -Be 0 + } + + It "Test-Json reports errors only for the invalid device in oneOf schema" { + $errorVar = $null + $result = Test-Json -Json $invalidOneOfDeviceJson -Schema $oneOfDeviceSchema -ErrorVariable errorVar -ErrorAction SilentlyContinue + + $result | Should -BeFalse + # Should not report errors for valid devices (Devices/0, /1, /2) + $falsePositives = $errorVar | Where-Object { $_.Exception.Message -match '/Devices/(0|1|2)' } + $falsePositives.Count | Should -Be 0 + # Should report errors only for the invalid device (Devices/3) + $relevantErrors = $errorVar | Where-Object { $_.Exception.Message -match '/Devices/3' } + $relevantErrors.Count | Should -BeGreaterThan 0 + } + + It "Test-Json does not report false positives for valid anyOf device matches" { + $errorVar = $null + $result = Test-Json -Json $validAnyOfDeviceJson -Schema $anyOfDeviceSchema -ErrorVariable errorVar -ErrorAction SilentlyContinue + + $result | Should -BeTrue + $errorVar.Count | Should -Be 0 + } + + It "Test-Json reports errors only for the invalid device in anyOf schema" { + $errorVar = $null + $result = Test-Json -Json $invalidAnyOfDeviceJson -Schema $anyOfDeviceSchema -ErrorVariable errorVar -ErrorAction SilentlyContinue + + $result | Should -BeFalse + # Should not report errors for valid devices (Devices/0, /1) + $falsePositives = $errorVar | Where-Object { $_.Exception.Message -match '/Devices/(0|1)' } + $falsePositives.Count | Should -Be 0 + # Should report errors only for the invalid device (Devices/2) + $relevantErrors = $errorVar | Where-Object { $_.Exception.Message -match '/Devices/2' } + $relevantErrors.Count | Should -BeGreaterThan 0 + } } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Update-FormatData.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Update-FormatData.Tests.ps1 index d7cfa1f3204..0c2969dfb2a 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Update-FormatData.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Update-FormatData.Tests.ps1 @@ -110,7 +110,7 @@ Describe "Update-FormatData with resources in CustomControls" -Tags "CI" { $templatePath = Join-Path $PSScriptRoot (Join-Path 'assets' 'UpdateFormatDataTests.format.ps1xml') $formatFilePath = Join-Path $TestDrive 'UpdateFormatDataTests.format.ps1xml' $ps = [powershell]::Create() - $iss = [system.management.automation.runspaces.initialsessionstate]::CreateDefault2() + $iss = [initialsessionstate]::CreateDefault2() $rs = [system.management.automation.runspaces.runspacefactory]::CreateRunspace($iss) $rs.Open() $ps.Runspace = $rs diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Update-TypeData.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Update-TypeData.Tests.ps1 index e2666ea6199..07a5f8ae2e6 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Update-TypeData.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Update-TypeData.Tests.ps1 @@ -37,7 +37,7 @@ Describe "Update-TypeData basic functionality" -Tags "CI" { BeforeEach { $ps = [powershell]::Create() - $iss = [system.management.automation.runspaces.initialsessionstate]::CreateDefault2() + $iss = [initialsessionstate]::CreateDefault2() $rs = [system.management.automation.runspaces.runspacefactory]::CreateRunspace($iss) $rs.Open() $ps.Runspace = $rs diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 index 1ddb9f10ba9..7c0fffa5c4a 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 @@ -1727,6 +1727,26 @@ Describe "Invoke-WebRequest tests" -Tags "Feature", "RequireAdminOnWindows" { $result.Files[0].Content | Should -Match $file1Contents } + It "Verifies Invoke-WebRequest -Form supports read-only file values" { + # Create a read-only test file + $readOnlyFile = Join-Path $TestDrive "readonly-test.txt" + "ReadOnly test content" | Out-File -FilePath $readOnlyFile -Encoding utf8 + Set-ItemProperty -Path $readOnlyFile -Name IsReadOnly -Value $true + + $form = @{TestFile = [System.IO.FileInfo]$readOnlyFile} + $uri = Get-WebListenerUrl -Test 'Multipart' + $response = Invoke-WebRequest -Uri $uri -Form $form -Method 'POST' + $result = $response.Content | ConvertFrom-Json + + $result.Headers.'Content-Type' | Should -Match 'multipart/form-data' + $result.Files.Count | Should -Be 1 + + $result.Files[0].Name | Should -BeExactly "TestFile" + $result.Files[0].FileName | Should -BeExactly "readonly-test.txt" + $result.Files[0].ContentType | Should -BeExactly 'application/octet-stream' + $result.Files[0].Content | Should -Match "ReadOnly test content" + } + It "Verifies Invoke-WebRequest -Form sets Content-Disposition FileName and FileNameStar." { $ContentDisposition = [System.Net.Http.Headers.ContentDispositionHeaderValue]::new("attachment") $ContentDisposition.FileName = $fileName @@ -3568,6 +3588,25 @@ Describe "Invoke-RestMethod tests" -Tags "Feature", "RequireAdminOnWindows" { $result.Files[0].Content | Should -Match $file1Contents } + It "Verifies Invoke-RestMethod -Form supports read-only file values" { + # Create a read-only test file + $readOnlyFile = Join-Path $TestDrive "readonly-test.txt" + "ReadOnly test content" | Out-File -FilePath $readOnlyFile -Encoding utf8 + Set-ItemProperty -Path $readOnlyFile -Name IsReadOnly -Value $true + + $form = @{TestFile = [System.IO.FileInfo]$readOnlyFile} + $uri = Get-WebListenerUrl -Test 'Multipart' + $result = Invoke-RestMethod -Uri $uri -Form $form -Method 'POST' + + $result.Headers.'Content-Type' | Should -Match 'multipart/form-data' + $result.Files.Count | Should -Be 1 + + $result.Files[0].Name | Should -Be "TestFile" + $result.Files[0].FileName | Should -Be "readonly-test.txt" + $result.Files[0].ContentType | Should -Be 'application/octet-stream' + $result.Files[0].Content | Should -Match "ReadOnly test content" + } + It "Verifies Invoke-RestMethod -Form sets Content-Disposition FileName and FileNameStar." { $ContentDisposition = [System.Net.Http.Headers.ContentDispositionHeaderValue]::new("attachment") $ContentDisposition.FileName = $fileName diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Write-Debug.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Write-Debug.Tests.ps1 index f93027cc578..951400349c3 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Write-Debug.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Write-Debug.Tests.ps1 @@ -34,4 +34,44 @@ Describe "Write-Debug tests" -Tags "CI" { $out = $p.StandardError.ReadToEnd() $out | Should -BeNullOrEmpty } + + It "'-Debug' should not trigger 'ShouldProcess'" { + $pwsh = [PowerShell]::Create() + $pwsh.AddScript(@' +function Test-DebugWithConfirm +{ + [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Low')] + Param () + + PROCESS + { + Write-Debug -Message "Debug_Message1" + If ($PSCmdlet.ShouldProcess('Doing the thing.','Proceed?','Ready to do the thing.')) + { + Write-Output 'success' + } + Write-Debug -Message "Debug_Message2" + } + + END {} +} +'@) + $pwsh.Invoke() + $pwsh.Commands.Clear() + $pwsh.Streams.ClearStreams() + + try { + $result = $pwsh.AddScript("Test-DebugWithConfirm -Debug").Invoke() + $result.Count | Should -BeExactly 1 + $result[0] | Should -BeExactly 'success' + + $pwsh.Streams.Error.Count | Should -BeExactly 0 + $pwsh.Streams.Debug.Count | Should -BeExactly 2 + $pwsh.Streams.Debug[0] | Should -BeExactly 'Debug_Message1' + $pwsh.Streams.Debug[1] | Should -BeExactly 'Debug_Message2' + } + finally { + $pwsh.Dispose() + } + } } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/clixml.tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/clixml.tests.ps1 index 27d5cfb8359..58f6a4f3353 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/clixml.tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/clixml.tests.ps1 @@ -286,7 +286,7 @@ Describe "Deserializing corrupted Cim classes should not instantiate non-Cim typ BeforeAll { # Only run on Windows platform. - # Ensure calc.exe is avaiable for test. + # Ensure calc.exe is available for test. $shouldRunTest = $IsWindows -and ((Get-Command calc.exe -ErrorAction SilentlyContinue) -ne $null) $skipNotWindows = ! $shouldRunTest if ( $shouldRunTest ) diff --git a/test/powershell/Modules/Microsoft.WSMan.Management/ConfigProvider.Tests.ps1 b/test/powershell/Modules/Microsoft.WSMan.Management/ConfigProvider.Tests.ps1 index 49d60cd1283..1845933e8f4 100644 --- a/test/powershell/Modules/Microsoft.WSMan.Management/ConfigProvider.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.WSMan.Management/ConfigProvider.Tests.ps1 @@ -1,6 +1,9 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] +param() + Describe "WSMan Config Provider" -Tag Feature,RequireAdminOnWindows { BeforeAll { #skip all tests on non-windows platform diff --git a/test/powershell/Modules/PSDiagnostics/PSDiagnostics.Tests.ps1 b/test/powershell/Modules/PSDiagnostics/PSDiagnostics.Tests.ps1 index 64fa8ba269f..30cc1b36fd4 100644 --- a/test/powershell/Modules/PSDiagnostics/PSDiagnostics.Tests.ps1 +++ b/test/powershell/Modules/PSDiagnostics/PSDiagnostics.Tests.ps1 @@ -9,7 +9,7 @@ Describe "PSDiagnostics cmdlets tests." -Tag "CI", "RequireAdminOnWindows" { $PSDefaultParameterValues["it:skip"] = $true } else{ - $LogSettingBak = Get-LogProperties -Name Microsoft-Windows-PowerShell/$LogType + $LogSettingBak = Get-LogProperties -Name PowerShellCore/$LogType } } AfterAll { @@ -20,37 +20,37 @@ Describe "PSDiagnostics cmdlets tests." -Tag "CI", "RequireAdminOnWindows" { } Context "Test for Enable-PSTrace and Disable-PSTrace cmdlets." { - It "Should enable $LogType logs for Microsoft-Windows-PowerShell." { - [XML]$CurrentSetting = & wevtutil gl Microsoft-Windows-PowerShell/$LogType /f:xml + It "Should enable $LogType logs for PowerShellCore." { + [XML]$CurrentSetting = & wevtutil gl PowerShellCore/$LogType /f:xml if($CurrentSetting.Channel.Enabled -eq 'true'){ - & wevtutil sl Microsoft-Windows-PowerShell/$LogType /e:false /q + & wevtutil sl PowerShellCore/$LogType /e:false /q } Enable-PSTrace -Force - [XML]$ExpectedOutput = & wevtutil gl Microsoft-Windows-PowerShell/$LogType /f:xml + [XML]$ExpectedOutput = & wevtutil gl PowerShellCore/$LogType /f:xml $ExpectedOutput.Channel.enabled | Should -BeExactly 'true' } - It "Should disable $LogType logs for Microsoft-Windows-PowerShell." { - [XML]$CurrentState = & wevtutil gl Microsoft-Windows-PowerShell/$LogType /f:xml + It "Should disable $LogType logs for PowerShellCore." { + [XML]$CurrentState = & wevtutil gl PowerShellCore/$LogType /f:xml if($CurrentState.channel.enabled -eq 'false'){ - & wevtutil sl Microsoft-Windows-PowerShell/$LogType /e:true /q + & wevtutil sl PowerShellCore/$LogType /e:true /q } Disable-PSTrace - [XML]$ExpectedOutput = & wevtutil gl Microsoft-Windows-PowerShell/$LogType /f:xml + [XML]$ExpectedOutput = & wevtutil gl PowerShellCore/$LogType /f:xml $ExpectedOutput.Channel.enabled | Should -Be 'false' } } Context "Test for Get-LogProperties cmdlet." { - It "Should return properties of $LogType logs for 'Microsoft-Windows-PowerShell'." { - [XML]$ExpectedOutput = wevtutil gl Microsoft-Windows-PowerShell/$LogType /f:xml + It "Should return properties of $LogType logs for 'PowerShellCore'." { + [XML]$ExpectedOutput = wevtutil gl PowerShellCore/$LogType /f:xml - $LogProperty = Get-LogProperties -Name Microsoft-Windows-PowerShell/$LogType + $LogProperty = Get-LogProperties -Name PowerShellCore/$LogType $LogProperty.Name | Should -Be $ExpectedOutput.channel.Name $LogProperty.Enabled | Should -Be $ExpectedOutput.channel.Enabled @@ -67,7 +67,7 @@ Describe "PSDiagnostics cmdlets tests." -Tag "CI", "RequireAdminOnWindows" { Context "Test for Set-LogProperties cmdlet." { BeforeAll { if ($IsWindows) { - [XML]$WevtUtilBefore = wevtutil gl Microsoft-Windows-PowerShell/$LogType /f:xml + [XML]$WevtUtilBefore = wevtutil gl PowerShellCore/$LogType /f:xml $LogPropertyToSet = [Microsoft.PowerShell.Diagnostics.LogDetails]::new($WevtUtilBefore.channel.Name, [bool]::Parse($WevtUtilBefore.channel.Enabled), $LogType, @@ -78,12 +78,12 @@ Describe "PSDiagnostics cmdlets tests." -Tag "CI", "RequireAdminOnWindows" { } } - It "Should invert AutoBackup setting of $LogType logs for 'Microsoft-Windows-PowerShell'." { + It "Should invert AutoBackup setting of $LogType logs for 'PowerShellCore'." { $LogPropertyToSet.AutoBackup = -not $LogPropertyToSet.AutoBackup Set-LogProperties -LogDetails $LogPropertyToSet -Force - [XML]$ExpectedOutput = & wevtutil gl Microsoft-Windows-PowerShell/$LogType /f:xml - (Get-LogProperties -Name Microsoft-Windows-PowerShell/$LogType).AutoBackup | Should -Be ([bool]::Parse($ExpectedOutput.Channel.Logging.AutoBackup)) + [XML]$ExpectedOutput = & wevtutil gl PowerShellCore/$LogType /f:xml + (Get-LogProperties -Name PowerShellCore/$LogType).AutoBackup | Should -Be ([bool]::Parse($ExpectedOutput.Channel.Logging.AutoBackup)) } It "Should throw exception for invalid LogName." { diff --git a/test/powershell/Modules/PSReadLine/PSReadLine.Tests.ps1 b/test/powershell/Modules/PSReadLine/PSReadLine.Tests.ps1 index 975f4f82da3..732404a6b36 100644 --- a/test/powershell/Modules/PSReadLine/PSReadLine.Tests.ps1 +++ b/test/powershell/Modules/PSReadLine/PSReadLine.Tests.ps1 @@ -12,13 +12,13 @@ Describe "PSReadLine" -tags "CI" { Import-Module PSReadLine $module = Get-Module PSReadLine $module.Name | Should -BeExactly 'PSReadLine' - $module.Version | Should -Match '^2.3.\d$' + $module.Version | Should -Match '^2.4.\d$' } It "Should be installed to `$PSHOME" { $module = Get-Module (Join-Path -Path $PSHOME -ChildPath "Modules" -AdditionalChildPath "PSReadLine") -ListAvailable $module.Name | Should -BeExactly 'PSReadLine' - $module.Version | Should -Match '^2.3.\d$' + $module.Version | Should -Match '^2.4.\d$' $module.Path | Should -Be (Join-Path -Path $PSHOME -ChildPath "Modules/PSReadLine/PSReadLine.psd1") } diff --git a/test/powershell/Modules/PowerShellGet/PowerShellGet.Tests.ps1 b/test/powershell/Modules/PowerShellGet/PowerShellGet.Tests.ps1 index 1fd935b22dd..a3a60fa359a 100644 --- a/test/powershell/Modules/PowerShellGet/PowerShellGet.Tests.ps1 +++ b/test/powershell/Modules/PowerShellGet/PowerShellGet.Tests.ps1 @@ -170,7 +170,7 @@ Describe "PowerShellGet - Module tests (Admin)" -Tags @('Feature', 'RequireAdmin } It "Should install a module correctly to the required location with AllUsers scope" { - Install-Module -Name $TestModule -Repository $RepositoryName -Scope AllUsers + Install-Module -Name $TestModule -Repository $RepositoryName -Scope AllUsers -ErrorAction Stop $module = Get-Module $TestModule -ListAvailable $module.Name | Should -Be $TestModule @@ -247,7 +247,7 @@ Describe "PowerShellGet - Script tests (Admin)" -Tags @('Feature', 'RequireAdmin } It "Should install a script correctly to the required location with AllUsers scope" { - Install-Script -Name $TestScript -Repository $RepositoryName -NoPathUpdate -Scope AllUsers + Install-Script -Name $TestScript -Repository $RepositoryName -NoPathUpdate -Scope AllUsers -ErrorAction Stop $installedScriptInfo = Get-InstalledScript -Name $TestScript $installedScriptInfo | Should -Not -BeNullOrEmpty diff --git a/test/powershell/dsc/dsc.profileresource.Tests.ps1 b/test/powershell/dsc/dsc.profileresource.Tests.ps1 new file mode 100644 index 00000000000..cb357c7350b --- /dev/null +++ b/test/powershell/dsc/dsc.profileresource.Tests.ps1 @@ -0,0 +1,214 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe "DSC PowerShell Profile Resource Tests" -Tag "CI" { + BeforeAll { + $DSC_ROOT = $env:DSC_ROOT + $skipCleanup = $false + + if (-not (Test-Path -Path $DSC_ROOT)) { + $skipCleanup = $true + throw "DSC_ROOT environment variable is not set or path does not exist." + } + + Write-Verbose "DSC_ROOT is set to $DSC_ROOT" -Verbose + + $originalPath = $env:PATH + + $pathSeparator = [System.IO.Path]::PathSeparator + $env:PATH += "$pathSeparator$DSC_ROOT" + $env:PATH += "$pathSeparator$PSHome" + + Write-Verbose "Updated PATH to include DSC_ROOT: $env:PATH" -Verbose + + # Ensure DSC v3 is available + if (-not (Get-Command -name dsc -CommandType Application -ErrorAction SilentlyContinue)) { + Get-ChildItem $DSC_ROOT -Recurse 'dsc' | ForEach-Object { + Write-Verbose "Found DSC executable at $($_.FullName)" -Verbose + } + throw "DSC v3 is not installed" + } + + $dscExe = Get-Command -name dsc -CommandType Application | Select-Object -First 1 + + $testProfileContent = "# Test profile content currentuser currenthost" + $testProfilePathCurrentUserCurrentHost = $PROFILE.CurrentUserCurrentHost + Copy-Item -Path $testProfilePathCurrentUserCurrentHost -Destination "$TestDrive/currentuser-currenthost-profile.bak" -Force -ErrorAction SilentlyContinue + New-Item -Path $testProfilePathCurrentUserCurrentHost -Value $testProfileContent -Force -ItemType File + + $testProfileContent = "# Test profile content currentuser allhosts" + $testProfilePathCurrentUserAllHosts = $PROFILE.CurrentUserAllHosts + Copy-Item -Path $testProfilePathCurrentUserAllHosts -Destination "$TestDrive/currentuser-allhosts-profile.bak" -Force -ErrorAction SilentlyContinue + New-Item -Path $testProfilePathCurrentUserAllHosts -Value $testProfileContent -Force -ItemType File + } + AfterAll { + if ($skipCleanup) { + return + } + + # Restore original profile + $testProfilePathCurrentUserCurrentHost = $PROFILE.CurrentUserCurrentHost + if (Test-Path "$TestDrive/currentuser-currenthost-profile.bak") { + Copy-Item -Path "$TestDrive/currentuser-currenthost-profile.bak" -Destination $testProfilePathCurrentUserCurrentHost -Force -ErrorAction SilentlyContinue + } + else { + Remove-Item $testProfilePathCurrentUserCurrentHost -Force -ErrorAction SilentlyContinue + } + + $testProfilePathCurrentUserAllHosts = $PROFILE.CurrentUserAllHosts + if (Test-Path "$TestDrive/currentuser-allhosts-profile.bak") { + Copy-Item -Path "$TestDrive/currentuser-allhosts-profile.bak" -Destination $testProfilePathCurrentUserAllHosts -Force -ErrorAction SilentlyContinue + } + else { + Remove-Item $testProfilePathCurrentUserAllHosts -Force -ErrorAction SilentlyContinue + } + + $env:PATH = $originalPath + Remove-Item -Path "$TestDrive/currentuser-currenthost-profile.bak" -Force -ErrorAction SilentlyContinue + Remove-Item -Path "$TestDrive/currentuser-allhosts-profile.bak" -Force -ErrorAction SilentlyContinue + } + + It 'DSC resource is located at $PSHome' { + $resourceFile = Join-Path -Path $PSHome -ChildPath 'pwsh.profile.resource.ps1' + $resourceFile | Should -Exist + + $resourceManifest = Join-Path -Path $PSHome -ChildPath 'pwsh.profile.dsc.resource.json' + $resourceManifest | Should -Exist + } + + It 'DSC resource can be found' { + (& $dscExe resource list -o json | ConvertFrom-Json | Select-Object -Property type).type | Should -Contain 'Microsoft.PowerShell/Profile' + } + + It 'DSC resource can set current user current host profile' { + $setOutput = (& $dscExe config set --file $PSScriptRoot/psprofile_currentuser_currenthost.dsc.yaml -o json) | ConvertFrom-Json + $expectedContent = "Write-Host 'Welcome to your PowerShell profile - CurrentUserCurrentHost!'" + $setOutput.results.result.afterState.content | Should -BeExactly $expectedContent + } + + It 'DSC resource can get current user current host profile' { + $getOutput = (& $dscExe config get --file $PSScriptRoot/psprofile_currentuser_currenthost.dsc.yaml -o json) | ConvertFrom-Json + $expectedContent = "Write-Host 'Welcome to your PowerShell profile - CurrentUserCurrentHost!'" + $getOutput.results.result.actualState.content | Should -BeExactly $expectedContent + } + + It 'DSC resource can set content as empty for current user current host profile' -Pending { + $setOutput = (& $dscExe config set --file $PSScriptRoot/psprofile_currentuser_currenthost_emptycontent.dsc.yaml -o json) | ConvertFrom-Json + $setOutput.results.result.afterState.content | Should -BeExactly '' + } + + It 'DSC resource can set current user all hosts profile' { + $setOutput = (& $dscExe config set --file $PSScriptRoot/psprofile_currentuser_allhosts.dsc.yaml -o json) | ConvertFrom-Json + $expectedContent = "Write-Host 'Welcome to your PowerShell profile - CurrentUserAllHosts!'" + $setOutput.results.result.afterState.content | Should -BeExactly $expectedContent + } + + It 'DSC resource can get current user all hosts profile' { + $getOutput = (& $dscExe config get --file $PSScriptRoot/psprofile_currentuser_allhosts.dsc.yaml -o json) | ConvertFrom-Json + $expectedContent = "Write-Host 'Welcome to your PowerShell profile - CurrentUserAllHosts!'" + $getOutput.results.result.actualState.content | Should -BeExactly $expectedContent + } + + It 'DSC resource can export all profiles' { + $exportOutput = (& $dscExe config export --file $PSScriptRoot/psprofile_export.dsc.yaml -o json) | ConvertFrom-Json + + $exportOutput.resources | Should -HaveCount 4 + + $exportOutput.resources | ForEach-Object { + $_.type | Should -Be 'Microsoft.PowerShell/Profile' + $_.name | Should -BeIn @('AllUsersCurrentHost', 'AllUsersAllHosts', 'CurrentUserCurrentHost', 'CurrentUserAllHosts') + } + } +} + +Describe "DSC PowerShell Profile resource elevated tests" -Tag "CI", 'RequireAdminOnWindows', 'RequireSudoOnUnix' { + BeforeAll { + $DSC_ROOT = $env:DSC_ROOT + $skipCleanup = $false + + if (-not (Test-Path -Path $DSC_ROOT)) { + $skipCleanup = $true + throw "DSC_ROOT environment variable is not set or path does not exist." + } + + Write-Verbose "DSC_ROOT is set to $DSC_ROOT" -Verbose + + $originalPath = $env:PATH + + $pathSeparator = [System.IO.Path]::PathSeparator + $env:PATH += "$pathSeparator$DSC_ROOT" + $env:PATH += "$pathSeparator$PSHome" + + Write-Verbose "Updated PATH to include DSC_ROOT: $env:PATH" -Verbose + + # Ensure DSC v3 is available + if (-not (Get-Command -name dsc -CommandType Application -ErrorAction SilentlyContinue)) { + Get-ChildItem $DSC_ROOT -Recurse 'dsc' | ForEach-Object { + Write-Verbose "Found DSC executable at $($_.FullName)" -Verbose + } + throw "DSC v3 is not installed" + } + + $dscExe = Get-Command -name dsc -CommandType Application | Select-Object -First 1 + + $testProfileContent = "# Test profile content allusers currenthost" + $testProfilePathAllUsersCurrentHost = $PROFILE.AllUsersCurrentHost + Copy-Item -Path $testProfilePathAllUsersCurrentHost -Destination "$TestDrive/allusers-currenthost-profile.bak" -Force -ErrorAction SilentlyContinue + New-Item -Path $testProfilePathAllUsersCurrentHost -Value $testProfileContent -Force -ItemType File + + $testProfileContent = "# Test profile content allusers allhosts" + $testProfilePathAllUsersAllHosts = $PROFILE.AllUsersAllHosts + Copy-Item -Path $testProfilePathAllUsersAllHosts -Destination "$TestDrive/allusers-allhosts-profile.bak" -Force -ErrorAction SilentlyContinue + New-Item -Path $testProfilePathAllUsersAllHosts -Value $testProfileContent -Force -ItemType File + } + AfterAll { + if ($skipCleanup) { + return + } + + $env:PATH = $originalPath + + $testProfilePathAllUsersCurrentHost = $PROFILE.AllUsersCurrentHost + if (Test-Path "$TestDrive/allusers-currenthost-profile.bak") { + Copy-Item -Path "$TestDrive/allusers-currenthost-profile.bak" -Destination $testProfilePathAllUsersCurrentHost -Force -ErrorAction SilentlyContinue + } + else { + Remove-Item $testProfilePathAllUsersCurrentHost -Force -ErrorAction SilentlyContinue + } + + $testProfilePathAllUsersAllHosts = $PROFILE.AllUsersAllHosts + if (Test-Path "$TestDrive/allusers-allhosts-profile.bak") { + Copy-Item -Path "$TestDrive/allusers-allhosts-profile.bak" -Destination $testProfilePathAllUsersAllHosts -Force -ErrorAction SilentlyContinue + } + else { + Remove-Item $testProfilePathAllUsersAllHosts -Force -ErrorAction SilentlyContinue + } + + Remove-Item -Path "$TestDrive/currentuser-allhosts-profile.bak" -Force -ErrorAction SilentlyContinue + Remove-Item -Path "$TestDrive/allusers-allhosts-profile.bak" -Force -ErrorAction SilentlyContinue + } + + It 'DSC resource can set all users all hosts profile' { + $setOutput = (& $dscExe config set --file $PSScriptRoot/psprofile_alluser_allhost.dsc.yaml -o json) | ConvertFrom-Json + $expectedContent = "Write-Host 'Welcome to your PowerShell profile - AllUsersAllHosts!'" + $setOutput.results.result.afterState.content | Should -BeExactly $expectedContent + } + + It 'DSC resource can get all users all hosts profile' { + $getOutput = (& $dscExe config get --file $PSScriptRoot/psprofile_alluser_allhost.dsc.yaml -o json) | ConvertFrom-Json + $expectedContent = "Write-Host 'Welcome to your PowerShell profile - AllUsersAllHosts!'" + $getOutput.results.result.actualState.content | Should -BeExactly $expectedContent + } + + It 'DSC resource can set all users current hosts profile' { + $setOutput = (& $dscExe config set --file $PSScriptRoot/psprofile_allusers_currenthost.dsc.yaml -o json) | ConvertFrom-Json + $expectedContent = "Write-Host 'Welcome to your PowerShell profile - AllUsersCurrentHost!'" + $setOutput.results.result.afterState.content | Should -BeExactly $expectedContent + } + + It 'DSC resource can get all users current hosts profile' { + $getOutput = (& $dscExe config get --file $PSScriptRoot/psprofile_allusers_currenthost.dsc.yaml -o json) | ConvertFrom-Json + $expectedContent = "Write-Host 'Welcome to your PowerShell profile - AllUsersCurrentHost!'" + $getOutput.results.result.actualState.content | Should -BeExactly $expectedContent + } +} diff --git a/test/powershell/dsc/psprofile_alluser_allhost.dsc.yaml b/test/powershell/dsc/psprofile_alluser_allhost.dsc.yaml new file mode 100644 index 00000000000..356119826c3 --- /dev/null +++ b/test/powershell/dsc/psprofile_alluser_allhost.dsc.yaml @@ -0,0 +1,9 @@ +# Set PowerShell profile content +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: +- name: PSProfile + type: Microsoft.PowerShell/Profile + properties: + profileType: AllUsersAllHosts + content: "Write-Host 'Welcome to your PowerShell profile - AllUsersAllHosts!'" + _exist: true diff --git a/test/powershell/dsc/psprofile_allusers_currenthost.dsc.yaml b/test/powershell/dsc/psprofile_allusers_currenthost.dsc.yaml new file mode 100644 index 00000000000..bc51f0a4392 --- /dev/null +++ b/test/powershell/dsc/psprofile_allusers_currenthost.dsc.yaml @@ -0,0 +1,9 @@ +# Set PowerShell profile content +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: +- name: PSProfile + type: Microsoft.PowerShell/Profile + properties: + profileType: AllUsersCurrentHost + content: "Write-Host 'Welcome to your PowerShell profile - AllUsersCurrentHost!'" + _exist: true diff --git a/test/powershell/dsc/psprofile_currentuser_allhosts.dsc.yaml b/test/powershell/dsc/psprofile_currentuser_allhosts.dsc.yaml new file mode 100644 index 00000000000..8ed8d98c3ab --- /dev/null +++ b/test/powershell/dsc/psprofile_currentuser_allhosts.dsc.yaml @@ -0,0 +1,9 @@ +# Set PowerShell profile content +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: +- name: PSProfile + type: Microsoft.PowerShell/Profile + properties: + profileType: CurrentUserAllHosts + content: "Write-Host 'Welcome to your PowerShell profile - CurrentUserAllHosts!'" + _exist: true diff --git a/test/powershell/dsc/psprofile_currentuser_currenthost.dsc.yaml b/test/powershell/dsc/psprofile_currentuser_currenthost.dsc.yaml new file mode 100644 index 00000000000..5a42c28eb96 --- /dev/null +++ b/test/powershell/dsc/psprofile_currentuser_currenthost.dsc.yaml @@ -0,0 +1,9 @@ +# Set PowerShell profile content +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: +- name: PSProfile + type: Microsoft.PowerShell/Profile + properties: + profileType: CurrentUserCurrentHost + content: "Write-Host 'Welcome to your PowerShell profile - CurrentUserCurrentHost!'" + _exist: true diff --git a/test/powershell/dsc/psprofile_currentuser_currenthost_emptycontent.yaml b/test/powershell/dsc/psprofile_currentuser_currenthost_emptycontent.yaml new file mode 100644 index 00000000000..76439387d2e --- /dev/null +++ b/test/powershell/dsc/psprofile_currentuser_currenthost_emptycontent.yaml @@ -0,0 +1,9 @@ +# Set PowerShell profile content +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: +- name: PSProfile + type: Microsoft.PowerShell/Profile + properties: + profileType: CurrentUserCurrentHost + content: '' + _exist: true diff --git a/test/powershell/dsc/psprofile_export.dsc.yaml b/test/powershell/dsc/psprofile_export.dsc.yaml new file mode 100644 index 00000000000..cf7881b5df8 --- /dev/null +++ b/test/powershell/dsc/psprofile_export.dsc.yaml @@ -0,0 +1,5 @@ +# Set PowerShell profile content +$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json +resources: +- name: PSProfile + type: Microsoft.PowerShell/Profile diff --git a/test/powershell/engine/Api/Serialization.Tests.ps1 b/test/powershell/engine/Api/Serialization.Tests.ps1 index 6b011ffc6ab..317a0907ca5 100644 --- a/test/powershell/engine/Api/Serialization.Tests.ps1 +++ b/test/powershell/engine/Api/Serialization.Tests.ps1 @@ -1,5 +1,9 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. + +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] +param() + Describe "Serialization Tests" -tags "CI" { BeforeAll { $testfileName="SerializationTest.txt" @@ -99,4 +103,3 @@ Describe "Serialization Tests" -tags "CI" { SerializeAndDeserialize($versionObject).TestScriptProperty | Should -Be $versionObject.TestScriptProperty } } - diff --git a/test/powershell/engine/Basic/CLRBinding.Tests.ps1 b/test/powershell/engine/Basic/CLRBinding.Tests.ps1 index 98a05d8518e..cb23fa168db 100644 --- a/test/powershell/engine/Basic/CLRBinding.Tests.ps1 +++ b/test/powershell/engine/Basic/CLRBinding.Tests.ps1 @@ -27,6 +27,12 @@ public class TestClass public static string StaticWithOptionalExpected() => StaticWithOptional(); public static string StaticWithOptional([Optional] string value) => value; + public static int PrimitiveTypeWithInDefault(in int value = default) => value; + + public static Guid ValueTypeWithInDefault(in Guid value = default) => value; + + public static string RefTypeWithInDefault(in string value = default) => value; + public object InstanceWithDefaultExpected() => InstanceWithDefault(); public object InstanceWithDefault(object value = null) => value; @@ -101,6 +107,21 @@ public class TestClassCstorWithOptional $actual | Should -Be $expected } + It "Binds to static method with primitive type with in modifier and default argument" { + $actual = [CLRBindingTests.TestClass]::PrimitiveTypeWithInDefault() + $actual | Should -Be 0 + } + + It "Binds to static method with value type with in modifier and default argument" { + $actual = [CLRBindingTests.TestClass]::ValueTypeWithInDefault() + $actual | Should -Be ([Guid]::Empty) + } + + It "Binds to static method with ref type with in modifier and default argument" { + $actual = [CLRBindingTests.TestClass]::RefTypeWithInDefault() + $null -eq $actual | Should -BeTrue + } + It "Binds to instance method with default argument" { $c = [CLRBindingTests.TestClass]::new() diff --git a/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 b/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 index 312c34b6706..7a757e6eac4 100644 --- a/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 +++ b/test/powershell/engine/Basic/DefaultCommands.Tests.ps1 @@ -337,6 +337,7 @@ Describe "Verify aliases and cmdlets" -Tags "CI" { "Cmdlet", "Get-PSSessionCapability", "", $($FullCLR -or $CoreWindows ), "", "", "None" "Cmdlet", "Get-PSSessionConfiguration", "", $($FullCLR -or $CoreWindows ), "", "", "None" "Cmdlet", "Get-PSSnapin", "", $($FullCLR ), "", "", "" +"Cmdlet", "Get-PSSubsystem", "", $( $CoreWindows -or $CoreUnix), "", "", "None" "Cmdlet", "Get-Random", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None" "Cmdlet", "Get-Runspace", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None" "Cmdlet", "Get-RunspaceDebug", "", $($FullCLR -or $CoreWindows -or $CoreUnix), "", "", "None" diff --git a/test/powershell/engine/Basic/NativeCommandEncoding.Tests.ps1 b/test/powershell/engine/Basic/NativeCommandEncoding.Tests.ps1 new file mode 100644 index 00000000000..5f044d059e0 --- /dev/null +++ b/test/powershell/engine/Basic/NativeCommandEncoding.Tests.ps1 @@ -0,0 +1,62 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe 'Native command output encoding tests' -Tags 'CI' { + BeforeAll { + $WriteConsoleOutPath = Join-Path $PSScriptRoot assets WriteConsoleOut.ps1 + $defaultEncoding = [Console]::OutputEncoding.WebName + } + + BeforeEach { + Clear-Variable -Name PSApplicationOutputEncoding + } + + AfterEach { + Clear-Variable -Name PSApplicationOutputEncoding + } + + It 'Defaults to [Console]::OutputEncoding if not set' { + $actual = pwsh -File $WriteConsoleOutPath -Value café -Encoding $defaultEncoding + $actual | Should -Be café + } + + It 'Defaults to [Console]::OutputEncoding if set to $null' { + $PSApplicationOutputEncoding = $null + $actual = pwsh -File $WriteConsoleOutPath -Value café -Encoding $defaultEncoding + $actual | Should -Be café + } + + It 'Uses scoped $PSApplicationOutputEncoding value' { + $PSApplicationOutputEncoding = [System.Text.Encoding]::Unicode + $actual = & { + $PSApplicationOutputEncoding = [System.Text.UTF8Encoding]::new() + pwsh -File $WriteConsoleOutPath -Value café -Encoding utf-8 + } + + $actual | Should -Be café + + # Will use UTF-16-LE hence the different values + $actual = pwsh -File $WriteConsoleOutPath -Value café -Encoding Unicode + $actual | Should -Be café + } + + It 'Uses variable in class method' { + class NativeEncodingTestClass { + static [string] RunTest([string]$Script) { + $PSApplicationOutputEncoding = [System.Text.Encoding]::Unicode + return pwsh -File $Script -Value café -Encoding unicode + } + } + + $actual = [NativeEncodingTestClass]::RunTest($WriteConsoleOutPath) + $actual | Should -Be café + } + + It 'Fails to set variable with invalid encoding object' { + $ps = [PowerShell]::Create() + $ps.AddScript('$PSApplicationOutputEncoding = "utf-8"').Invoke() + + $ps.Streams.Error.Count | Should -Be 1 + [string]$ps.Streams.Error[0] | Should -Be 'Cannot convert the "utf-8" value of type "System.String" to type "System.Text.Encoding".' + } +} diff --git a/test/powershell/engine/Basic/Telemetry.Tests.ps1 b/test/powershell/engine/Basic/Telemetry.Tests.ps1 index 2378b9e5a66..0da08316a56 100644 --- a/test/powershell/engine/Basic/Telemetry.Tests.ps1 +++ b/test/powershell/engine/Basic/Telemetry.Tests.ps1 @@ -5,8 +5,53 @@ # these tests aren't going to check that telemetry is being sent # only that we're not treating the telemetry.uuid file correctly +function Get-OSTelemetryLevel { + <# + .SYNOPSIS + Returns the effective Windows Telemetry level (0-3). + Logic: Checks GPO overrides, then System preferences, then defaults to 1. + #> + + $gpoPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\DataCollection" + $sysPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\DataCollection" + $valueName = "AllowTelemetry" + + # 1. Check the "Managed" Policy (Group Policy) + if (Test-Path $gpoPath) { + $gpoValue = Get-ItemProperty -Path $gpoPath -Name $valueName -ErrorAction SilentlyContinue + if ($gpoValue -and $gpoValue.$valueName) { + return [int]$gpoValue.$valueName + } + } + + # 2. Check the "User/System" Preference (Settings App) + if (Test-Path $sysPath) { + $sysValue = Get-ItemProperty -Path $sysPath -Name $valueName -ErrorAction SilentlyContinue + if ($sysValue -and $sysValue.$valueName) { + return [int]$sysValue.$valueName + } + } + + # 3. Fallback to OS Default (Basic/Required) + return 1 +} + Describe "Telemetry for shell startup" -Tag CI { BeforeAll { + $skipTelemetryTests = $false + + if ($IsWindows) { + ## Skip telemetry tests if the OS telemetry level is less than 2 (Enhanced) -- PS telemetry is disabled in this case. + $osTelemetryLevel = Get-OSTelemetryLevel + $skipTelemetryTests = $osTelemetryLevel -lt 2 + } + + if ($skipTelemetryTests) { + $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + $PSDefaultParameterValues["it:skip"] = $true + return + } + # if the telemetry file exists, move it out of the way # the member is internal, but we can retrieve it via reflection $cacheDir = [System.Management.Automation.Platform].GetField("CacheDirectory","NonPublic,Static").GetValue($null) @@ -23,6 +68,11 @@ Describe "Telemetry for shell startup" -Tag CI { } AfterAll { + if ($skipTelemetryTests) { + $global:PSDefaultParameterValues = $originalDefaultParameterValues + return + } + # check and reset the telemetry.uuid file if ( $uuidFileExists ) { if ( Test-Path -Path "${uuidPath}.original" ) { diff --git a/test/powershell/engine/Basic/ValidateAttributes.Tests.ps1 b/test/powershell/engine/Basic/ValidateAttributes.Tests.ps1 index c8d56786d51..ff4ad0ebcac 100644 --- a/test/powershell/engine/Basic/ValidateAttributes.Tests.ps1 +++ b/test/powershell/engine/Basic/ValidateAttributes.Tests.ps1 @@ -6,33 +6,33 @@ Describe 'Validate Attributes Tests' -Tags 'CI' { BeforeAll { $testCases = @( @{ - ScriptBlock = { function foo { param([ValidateCount(-1,2)] [string[]] $bar) }; foo } + ScriptBlock = { function Test-ArrayCount { param([ValidateCount(-1,2)] [string[]] $Items) }; Test-ArrayCount } FullyQualifiedErrorId = "ExceptionConstructingAttribute" InnerErrorId = "" } @{ - ScriptBlock = { function foo { param([ValidateCount(1,-1)] [string[]] $bar) }; foo } + ScriptBlock = { function Test-ArrayCount { param([ValidateCount(1,-1)] [string[]] $Items) }; Test-ArrayCount } FullyQualifiedErrorId = "ExceptionConstructingAttribute" InnerErrorId = "" } @{ - ScriptBlock = { function foo { param([ValidateCount(2, 1)] [string[]] $bar) }; foo } + ScriptBlock = { function Test-ArrayCount { param([ValidateCount(2, 1)] [string[]] $Items) }; Test-ArrayCount } FullyQualifiedErrorId = "ValidateRangeMaxLengthSmallerThanMinLength" InnerErrorId = "" } @{ - ScriptBlock = { function foo { param([ValidateCount(2, 2)] [string[]] $bar) }; foo 1 } - FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + ScriptBlock = { function Test-ArrayCount { param([ValidateCount(2, 2)] [string[]] $Items) }; Test-ArrayCount 1 } + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-ArrayCount" InnerErrorId = "ValidateCountExactFailure" } @{ - ScriptBlock = { function foo { param([ValidateCount(2, 3)] [string[]] $bar) }; foo 1 } - FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + ScriptBlock = { function Test-ArrayCount { param([ValidateCount(2, 3)] [string[]] $Items) }; Test-ArrayCount 1 } + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-ArrayCount" InnerErrorId = "ValidateCountMinMaxFailure" } @{ - ScriptBlock = { function foo { param([ValidateCount(2, 3)] [string[]] $bar) }; foo 1,2,3,4 } - FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + ScriptBlock = { function Test-ArrayCount { param([ValidateCount(2, 3)] [string[]] $Items) }; Test-ArrayCount 1,2,3,4 } + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-ArrayCount" InnerErrorId = "ValidateCountMinMaxFailure" } ) @@ -41,14 +41,14 @@ Describe 'Validate Attributes Tests' -Tags 'CI' { It 'Exception: <FullyQualifiedErrorId>:<InnerErrorId>' -TestCases $testCases { param($ScriptBlock, $FullyQualifiedErrorId, $InnerErrorId) - $ScriptBlock | Should -Throw -ErrorId $FullyQualifiedErrorId + $err = $ScriptBlock | Should -Throw -ErrorId $FullyQualifiedErrorId -PassThru if ($InnerErrorId) { - $error[0].exception.innerexception.errorrecord.FullyQualifiedErrorId | Should -Be $InnerErrorId + $err.exception.innerexception.errorrecord.FullyQualifiedErrorId | Should -Be $InnerErrorId } } It 'No Exception: valid argument count' { - { function foo { param([ValidateCount(2, 4)] [string[]] $bar) }; foo 1,2,3,4 } | Should -Not -Throw + { function Test-ArrayCount { param([ValidateCount(2, 4)] [string[]] $Items) }; Test-ArrayCount 1,2,3,4 } | Should -Not -Throw } } @@ -56,22 +56,22 @@ Describe 'Validate Attributes Tests' -Tags 'CI' { BeforeAll { $testCases = @( @{ - ScriptBlock = { function foo { param([ValidateRange('xPositive')] $bar) }; foo } + ScriptBlock = { function Test-NumericRange { param([ValidateRange('xPositive')] $Number) }; Test-NumericRange } FullyQualifiedErrorId = "ExceptionConstructingAttribute" InnerErrorId = "SubstringDisambiguationEnumParseThrewAnException" } @{ - ScriptBlock = { function foo { param([ValidateRange(2,1)] [int] $bar) }; foo } + ScriptBlock = { function Test-NumericRange { param([ValidateRange(2,1)] [int] $Number) }; Test-NumericRange } FullyQualifiedErrorId = "MaxRangeSmallerThanMinRange" InnerErrorId = "" } @{ - ScriptBlock = { function foo { param([ValidateRange("one",10)] $bar) }; foo } + ScriptBlock = { function Test-NumericRange { param([ValidateRange("one",10)] $Number) }; Test-NumericRange } FullyQualifiedErrorId = "MinRangeNotTheSameTypeOfMaxRange" InnerErrorId = "" } @{ - ScriptBlock = { function foo { param([ValidateRange(1,"two")] $bar) }; foo } + ScriptBlock = { function Test-NumericRange { param([ValidateRange(1,"two")] $Number) }; Test-NumericRange } FullyQualifiedErrorId = "MinRangeNotTheSameTypeOfMaxRange" InnerErrorId = "" } @@ -81,9 +81,9 @@ Describe 'Validate Attributes Tests' -Tags 'CI' { It 'Exception: <FullyQualifiedErrorId>:<InnerErrorId>' -TestCases $testCases { param($ScriptBlock, $FullyQualifiedErrorId, $InnerErrorId) - $ScriptBlock | Should -Throw -ErrorId $FullyQualifiedErrorId + $err = $ScriptBlock | Should -Throw -ErrorId $FullyQualifiedErrorId -PassThru if ($InnerErrorId) { - $error[0].exception.innerexception.errorrecord.FullyQualifiedErrorId | Should -Be $InnerErrorId + $err.exception.innerexception.errorrecord.FullyQualifiedErrorId | Should -Be $InnerErrorId } } } @@ -91,25 +91,25 @@ Describe 'Validate Attributes Tests' -Tags 'CI' { BeforeAll { $testCases = @( @{ - ScriptBlock = { function foo { param([ValidateRange(1,10)] [int] $bar) }; foo -1 } - FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + ScriptBlock = { function Test-NumericRange { param([ValidateRange(1,10)] [int] $Number) }; Test-NumericRange -1 } + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-NumericRange" InnerErrorId = "ValidateRangeTooSmall" } @{ - ScriptBlock = { function foo { param([ValidateRange(1,10)] [int] $bar) }; foo 11 } - FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + ScriptBlock = { function Test-NumericRange { param([ValidateRange(1,10)] [int] $Number) }; Test-NumericRange 11 } + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-NumericRange" InnerErrorId = "ValidateRangeTooBig" } @{ - ScriptBlock = { function foo { param([ValidateRange(1,10)] $bar) }; foo "one" } - FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + ScriptBlock = { function Test-NumericRange { param([ValidateRange(1,10)] $Number) }; Test-NumericRange "one" } + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-NumericRange" InnerErrorId = "ValidationRangeElementType" } ) $validTestCases = @( @{ - ScriptBlock = { function foo { param([ValidateRange(1,10)] [int] $bar) }; foo 5 } + ScriptBlock = { function Test-NumericRange { param([ValidateRange(1,10)] [int] $Number) }; Test-NumericRange 5 } } ) } @@ -117,9 +117,9 @@ Describe 'Validate Attributes Tests' -Tags 'CI' { It 'Exception: <FullyQualifiedErrorId>:<InnerErrorId>' -TestCases $testCases { param($ScriptBlock, $FullyQualifiedErrorId, $InnerErrorId) - $ScriptBlock | Should -Throw -ErrorId $FullyQualifiedErrorId + $err = $ScriptBlock | Should -Throw -ErrorId $FullyQualifiedErrorId -PassThru if ($InnerErrorId) { - $error[0].exception.innerexception.errorrecord.FullyQualifiedErrorId | Should -Be $InnerErrorId + $err.exception.innerexception.errorrecord.FullyQualifiedErrorId | Should -Be $InnerErrorId } } @@ -133,115 +133,115 @@ Describe 'Validate Attributes Tests' -Tags 'CI' { BeforeAll { $testCases = @( @{ - ScriptBlock = { function foo { param([ValidateRange("Positive")] [int] $bar) }; foo -1 } + ScriptBlock = { function Test-NumericRange { param([ValidateRange("Positive")] [int] $Number) }; Test-NumericRange -1 } RangeType = "Positive" - FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-NumericRange" InnerErrorId = "ValidateRangePositiveFailure" } @{ - ScriptBlock = { function foo { param([ValidateRange("Positive")] [int] $bar) }; foo 0 } + ScriptBlock = { function Test-NumericRange { param([ValidateRange("Positive")] [int] $Number) }; Test-NumericRange 0 } RangeType = "Positive" - FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-NumericRange" InnerErrorId = "ValidateRangePositiveFailure" } @{ - ScriptBlock = { function foo { param([ValidateRange("Positive")] $bar) }; foo "one" } + ScriptBlock = { function Test-NumericRange { param([ValidateRange("Positive")] $Number) }; Test-NumericRange "one" } RangeType = "Positive" - FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-NumericRange" InnerErrorId = "" } @{ - ScriptBlock = { function foo { param([ValidateRange('NonNegative')] [int] $bar) }; foo -1 } + ScriptBlock = { function Test-NumericRange { param([ValidateRange('NonNegative')] [int] $Number) }; Test-NumericRange -1 } RangeType = "NonNegative" - FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-NumericRange" InnerErrorId = "ValidateRangeNonNegativeFailure" } @{ - ScriptBlock = { function foo { param([ValidateRange('NonNegative')] $bar) }; foo "one" } + ScriptBlock = { function Test-NumericRange { param([ValidateRange('NonNegative')] $Number) }; Test-NumericRange "one" } RangeType = "NonNegative" - FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-NumericRange" InnerErrorId = "" } @{ - ScriptBlock = { function foo { param([ValidateRange('Negative')] [int] $bar) }; foo 1 } + ScriptBlock = { function Test-NumericRange { param([ValidateRange('Negative')] [int] $Number) }; Test-NumericRange 1 } RangeType = "Negative" - FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-NumericRange" InnerErrorId = "ValidateRangeNegativeFailure" } @{ - ScriptBlock = { function foo { param([ValidateRange('Negative')] [int] $bar) }; foo 0 } + ScriptBlock = { function Test-NumericRange { param([ValidateRange('Negative')] [int] $Number) }; Test-NumericRange 0 } RangeType = "Negative" - FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-NumericRange" InnerErrorId = "ValidateRangeNegativeFailure" } @{ - ScriptBlock = { function foo { param([ValidateRange('Negative')] $bar) }; foo "one" } + ScriptBlock = { function Test-NumericRange { param([ValidateRange('Negative')] $Number) }; Test-NumericRange "one" } RangeType = "Negative" - FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-NumericRange" InnerErrorId = "" } @{ - ScriptBlock = { function foo { param([ValidateRange('NonPositive')] $bar) }; foo 1 } + ScriptBlock = { function Test-NumericRange { param([ValidateRange('NonPositive')] $Number) }; Test-NumericRange 1 } RangeType = "NonPositive" - FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-NumericRange" InnerErrorId = "ValidateRangeNonPositiveFailure" } @{ - ScriptBlock = { function foo { param([ValidateRange('NonPositive')] $bar) }; foo "one" } + ScriptBlock = { function Test-NumericRange { param([ValidateRange('NonPositive')] $Number) }; Test-NumericRange "one" } RangeType = "NonPositive" - FullyQualifiedErrorId = "ParameterArgumentValidationError,foo" + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-NumericRange" InnerErrorId = "" } ) $validTestCases = @( @{ - ScriptBlock = { function foo { param([ValidateRange("Positive")] [int] $bar) }; foo 15 } + ScriptBlock = { function Test-NumericRange { param([ValidateRange("Positive")] [int] $Number) }; Test-NumericRange 15 } RangeType = "Positive" TestValue = 15 } @{ - ScriptBlock = { function foo { param([ValidateRange("Positive")] [double]$bar) }; foo ([double]::MaxValue) }; + ScriptBlock = { function Test-NumericRange { param([ValidateRange("Positive")] [double]$Number) }; Test-NumericRange ([double]::MaxValue) }; RangeType = "Positive" TestValue = [double]::MaxValue } @{ - ScriptBlock = { function foo { param([ValidateRange('NonNegative')] [int] $bar) }; foo 0 } + ScriptBlock = { function Test-NumericRange { param([ValidateRange('NonNegative')] [int] $Number) }; Test-NumericRange 0 } RangeType = "NonNegative" TestValue = 0 } @{ - ScriptBlock = { function foo { param([ValidateRange('NonNegative')] [int] $bar) }; foo 15 } + ScriptBlock = { function Test-NumericRange { param([ValidateRange('NonNegative')] [int] $Number) }; Test-NumericRange 15 } RangeType = "NonNegative" TestValue = 15 } @{ - ScriptBlock = { function foo { param([ValidateRange('NonNegative')] [double]$bar) }; foo ([double]::MaxValue) }; + ScriptBlock = { function Test-NumericRange { param([ValidateRange('NonNegative')] [double]$Number) }; Test-NumericRange ([double]::MaxValue) }; RangeType = "NonNegative" TestValue = [double]::MaxValue } @{ - ScriptBlock = { function foo { param([ValidateRange('Negative')] [int] $bar) }; foo -15 } + ScriptBlock = { function Test-NumericRange { param([ValidateRange('Negative')] [int] $Number) }; Test-NumericRange -15 } RangeType = "Negative" TestValue = -15 } @{ - ScriptBlock = { function foo { param([ValidateRange('Negative')] [double]$bar) }; foo ([double]::MinValue) }; + ScriptBlock = { function Test-NumericRange { param([ValidateRange('Negative')] [double]$Number) }; Test-NumericRange ([double]::MinValue) }; TestValue = [double]::MinValue RangeType = "Negative" } @{ - ScriptBlock = { function foo { param([ValidateRange('NonPositive')] [int] $bar) }; foo 0 } + ScriptBlock = { function Test-NumericRange { param([ValidateRange('NonPositive')] [int] $Number) }; Test-NumericRange 0 } RangeType = "NonPositive" TestValue = 0 } @{ - ScriptBlock = { function foo { param([ValidateRange('NonPositive')] [int] $bar) }; foo -15 } + ScriptBlock = { function Test-NumericRange { param([ValidateRange('NonPositive')] [int] $Number) }; Test-NumericRange -15 } RangeType = "NonPositive" TestValue = -15 } @{ - ScriptBlock = { function foo { param([ValidateRange('NonPositive')] [double]$bar) }; foo ([double]::MinValue) } + ScriptBlock = { function Test-NumericRange { param([ValidateRange('NonPositive')] [double]$Number) }; Test-NumericRange ([double]::MinValue) } RangeType = "NonPositive" TestValue = [double]::MinValue } @@ -251,9 +251,9 @@ Describe 'Validate Attributes Tests' -Tags 'CI' { It 'Exception: <FullyQualifiedErrorId>:<InnerErrorId>, RangeType: <RangeType>' -TestCases $testCases { param($ScriptBlock, $RangeType, $FullyQualifiedErrorId, $InnerErrorId) - $ScriptBlock | Should -Throw -ErrorId $FullyQualifiedErrorId + $err = $ScriptBlock | Should -Throw -ErrorId $FullyQualifiedErrorId -PassThru if ($InnerErrorId) { - $error[0].exception.innerexception.errorrecord.FullyQualifiedErrorId | Should -Be $InnerErrorId + $err.exception.innerexception.errorrecord.FullyQualifiedErrorId | Should -Be $InnerErrorId } } @@ -263,6 +263,70 @@ Describe 'Validate Attributes Tests' -Tags 'CI' { } } + Context "ValidateLength" { + BeforeAll { + $testCases = @( + @{ + ScriptBlock = { function Test-StringLength { param([ValidateLength(2, 5)] [string] $InputString) }; Test-StringLength "a" } + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-StringLength" + InnerErrorId = "ValidateLengthMinLengthFailure" + } + @{ + ScriptBlock = { function Test-StringLength { param([ValidateLength(2, 5)] [string] $InputString) }; Test-StringLength "abcdef" } + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-StringLength" + InnerErrorId = "ValidateLengthMaxLengthFailure" + } + @{ + ScriptBlock = { function Test-StringLength { param([ValidateLength(2, 5)] $InputString) }; Test-StringLength 123 } + FullyQualifiedErrorId = "ParameterArgumentValidationError,Test-StringLength" + InnerErrorId = "ValidateLengthNotString" + } + ) + + $validTestCases = @( + @{ + ScriptBlock = { function Test-StringLength { param([ValidateLength(2, 5)] [string] $InputString) }; Test-StringLength "abc" } + } + @{ + ScriptBlock = { function Test-StringLength { param([ValidateLength(2, 5)] [string] $InputString) }; Test-StringLength "ab" } + } + @{ + ScriptBlock = { function Test-StringLength { param([ValidateLength(2, 5)] [string] $InputString) }; Test-StringLength "abcde" } + } + ) + } + + It 'Exception: <FullyQualifiedErrorId>:<InnerErrorId>' -TestCases $testCases { + param($ScriptBlock, $FullyQualifiedErrorId, $InnerErrorId) + + $err = $ScriptBlock | Should -Throw -ErrorId $FullyQualifiedErrorId -PassThru + if ($InnerErrorId) { + $err.exception.innerexception.errorrecord.FullyQualifiedErrorId | Should -Be $InnerErrorId + } + } + + It 'No Exception: valid string length' -TestCases $validTestCases { + param($ScriptBlock) + $ScriptBlock | Should -Not -Throw + } + + It 'ValidateLength error message should be properly formatted' { + function Test-ValidateLengthMax { param([ValidateLength(0,2)] [string] $Value) $Value } + function Test-ValidateLengthMin { param([ValidateLength(5,10)] [string] $Value) $Value } + + $TestStringTooLong = "11111" + $TestStringTooShort = "123" + $ExpectedMaxLength = 2 + $ExpectedMinLength = 5 + + $err = { Test-ValidateLengthMax $TestStringTooLong } | Should -Throw -ErrorId "ParameterArgumentValidationError,Test-ValidateLengthMax" -PassThru + $err.Exception.InnerException.Message | Should -Match ".+\($($TestStringTooLong.Length)\).+\`"$ExpectedMaxLength\`"" + + $err = { Test-ValidateLengthMin $TestStringTooShort } | Should -Throw -ErrorId "ParameterArgumentValidationError,Test-ValidateLengthMin" -PassThru + $err.Exception.InnerException.Message | Should -Match ".+\($($TestStringTooShort.Length)\).+\`"$ExpectedMinLength\`"" + } + } + Context "ValidateNotNull, ValidateNotNullOrEmpty, ValidateNotNullOrWhiteSpace and Not-Null-Or-Empty check for Mandatory parameter" { BeforeAll { diff --git a/test/powershell/engine/Basic/assets/WriteConsoleOut.ps1 b/test/powershell/engine/Basic/assets/WriteConsoleOut.ps1 new file mode 100644 index 00000000000..ef82907a17f --- /dev/null +++ b/test/powershell/engine/Basic/assets/WriteConsoleOut.ps1 @@ -0,0 +1,23 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +[CmdletBinding()] +param ( + [Parameter(Mandatory)] + [string] + $Value, + + [Parameter(Mandatory)] + [string] + $Encoding +) + +$enc = [System.Text.Encoding]::GetEncoding($Encoding) +$data = $enc.GetBytes($Value) + +$outStream = [System.Console]::OpenStandardOutput() +try { + $outStream.Write($data, 0, $data.Length) +} finally { + $outStream.Dispose() +} diff --git a/test/powershell/engine/ETS/CimAdapter.Tests.ps1 b/test/powershell/engine/ETS/CimAdapter.Tests.ps1 index 1e71bc28b95..3cfff4bd9e4 100644 --- a/test/powershell/engine/ETS/CimAdapter.Tests.ps1 +++ b/test/powershell/engine/ETS/CimAdapter.Tests.ps1 @@ -3,6 +3,8 @@ Describe "CIM Objects are adapted properly" -Tag @("CI") { BeforeAll { + $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + function getIndex { param([string[]]$strings,[string]$pattern) @@ -32,7 +34,7 @@ Describe "CIM Objects are adapted properly" -Tag @("CI") { } } AfterAll { - $PSDefaultParameterValues.Remove("it:pending") + $global:PSDefaultParameterValues = $originalDefaultParameterValues } It "Namespace-qualified Win32_Process is present" -Skip:(!$IsWindows) { diff --git a/test/powershell/engine/ExperimentalFeature/assets/ExpTest/ExpTest.cs b/test/powershell/engine/ExperimentalFeature/assets/ExpTest/ExpTest.cs index 219c01058e9..73bb24ccd08 100644 --- a/test/powershell/engine/ExperimentalFeature/assets/ExpTest/ExpTest.cs +++ b/test/powershell/engine/ExperimentalFeature/assets/ExpTest/ExpTest.cs @@ -151,7 +151,7 @@ public class SaveMyFileCommand : PSCmdlet [Parameter] public string FileName { get; set; } - + [Experimental("ExpTest.FeatureOne", ExperimentAction.Show)] [Parameter] public string Destination { get; set; } diff --git a/test/powershell/engine/Formatting/BugFix.Tests.ps1 b/test/powershell/engine/Formatting/BugFix.Tests.ps1 index 5be7797134c..8b1ecdaccd9 100644 --- a/test/powershell/engine/Formatting/BugFix.Tests.ps1 +++ b/test/powershell/engine/Formatting/BugFix.Tests.ps1 @@ -63,3 +63,17 @@ Describe "Hidden properties should not be returned by the 'FirstOrDefault' primi $outstring.Trim() | Should -BeExactly "Param$([System.Environment]::NewLine)-----$([System.Environment]::NewLine)Foo" } } + +Describe "'Format-Table/List/Custom -Property' should not throw NullRef exception" -Tag CI { + + It "'<Command> -Property' requires value to be not null and not empty" -TestCases @( + @{ Command = "Format-Table"; NameInErrorId = "FormatTableCommand" } + @{ Command = "Format-List"; NameInErrorId = "FormatListCommand" } + @{ Command = "Format-Custom"; NameInErrorId = "FormatCustomCommand" } + ) { + param($Command, $NameInErrorId) + + { Get-Process -Id $PID | & $Command -Property @() } | Should -Throw -ErrorId "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.$NameInErrorId" + { Get-Process -Id $PID | & $Command -Property $null } | Should -Throw -ErrorId "ParameterArgumentValidationError,Microsoft.PowerShell.Commands.$NameInErrorId" + } +} diff --git a/test/powershell/engine/Formatting/ErrorView.Tests.ps1 b/test/powershell/engine/Formatting/ErrorView.Tests.ps1 index 519b9403b9e..6af2b5fb504 100644 --- a/test/powershell/engine/Formatting/ErrorView.Tests.ps1 +++ b/test/powershell/engine/Formatting/ErrorView.Tests.ps1 @@ -165,6 +165,387 @@ Describe 'Tests for $ErrorView' -Tag CI { $e | Out-String | Should -BeLike '*ParserError*' } + It 'Parser TargetObject shows Line information' { + $expected = (@( + ": " + "Line |" + " 1 | This is the line with the error" + " | Test Parser Error" + ) -join ([Environment]::NewLine)).TrimEnd() + $e = { + [CmdletBinding()] + param () + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.Exception]::new('Test Parser Error'), + 'ParserErrorText', + [System.Management.Automation.ErrorCategory]::ParserError, + @{ + Line = 1 + LineText = 'This is the line with the error' + } + ) + ) + } | Should -Throw -PassThru + + $actual = ($e | Out-String).TrimEnd() + $actual | Should -BeExactly $expected + } + + It 'Parser TargetObject shows File information' { + $expected = (@( + ": MyFile.ps1" + "Line |" + " 1 | This is the line with the error" + " | Test Parser Error" + ) -join ([Environment]::NewLine)).TrimEnd() + $e = { + [CmdletBinding()] + param () + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.Exception]::new('Test Parser Error'), + 'ParserErrorText', + [System.Management.Automation.ErrorCategory]::ParserError, + @{ + File = 'MyFile.ps1' + Line = 1 + LineText = 'This is the line with the error' + } + ) + ) + } | Should -Throw -PassThru + + $actual = ($e | Out-String).TrimEnd() + $actual | Should -BeExactly $expected + } + + It 'Parser TargetObject has StartColumn' { + $expected = (@( + ": " + "Line |" + " 5 | This is the line with the error" + " | ~~~~~~~~~~~~~~" + " | Test Parser Error" + ) -join ([Environment]::NewLine)).TrimEnd() + $e = { + [CmdletBinding()] + param () + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.Exception]::new('Test Parser Error'), + 'ParserErrorText', + [System.Management.Automation.ErrorCategory]::ParserError, + @{ + + Line = 5 + LineText = 'This is the line with the error' + StartColumn = 18 + } + ) + ) + } | Should -Throw -PassThru + + $actual = ($e | Out-String).TrimEnd() + $actual | Should -BeExactly $expected + } + + It 'Parser TargetObject has StartColumn and EndColumn' { + $expected = (@( + ": " + "Line |" + " 5 | This is the line with the error" + " | ~~~~" + " | Test Parser Error" + ) -join ([Environment]::NewLine)).TrimEnd() + $e = { + [CmdletBinding()] + param () + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.Exception]::new('Test Parser Error'), + 'ParserErrorText', + [System.Management.Automation.ErrorCategory]::ParserError, + @{ + + Line = 5 + LineText = 'This is the line with the error' + StartColumn = 18 + EndColumn = 22 + } + ) + ) + } | Should -Throw -PassThru + + $actual = ($e | Out-String).TrimEnd() + $actual | Should -BeExactly $expected + } + + It 'Parser TargetObject has StartColumn at end of the line' { + $expected = (@( + ": " + "Line |" + " 5 | This is the line with the error" + " | ~" + " | Test Parser Error" + ) -join ([Environment]::NewLine)).TrimEnd() + $e = { + [CmdletBinding()] + param () + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.Exception]::new('Test Parser Error'), + 'ParserErrorText', + [System.Management.Automation.ErrorCategory]::ParserError, + @{ + + Line = 5 + LineText = 'This is the line with the error' + StartColumn = 31 + } + ) + ) + } | Should -Throw -PassThru + + $actual = ($e | Out-String).TrimEnd() + $actual | Should -BeExactly $expected + } + + + It 'Parser TargetObject has StartColumn at end of the line with EndColumn' { + $expected = (@( + ": " + "Line |" + " 5 | This is the line with the error" + " | ~" + " | Test Parser Error" + ) -join ([Environment]::NewLine)).TrimEnd() + $e = { + [CmdletBinding()] + param () + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.Exception]::new('Test Parser Error'), + 'ParserErrorText', + [System.Management.Automation.ErrorCategory]::ParserError, + @{ + + Line = 5 + LineText = 'This is the line with the error' + StartColumn = 31 + EndColumn = 32 + } + ) + ) + } | Should -Throw -PassThru + + $actual = ($e | Out-String).TrimEnd() + $actual | Should -BeExactly $expected + } + + It 'Parser TargetObject ignores EndColumn if no StartColumn' { + $expected = (@( + ": " + "Line |" + " 1 | This is the line with the error" + " | Test Parser Error" + ) -join ([Environment]::NewLine)).TrimEnd() + $e = { + [CmdletBinding()] + param () + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.Exception]::new('Test Parser Error'), + 'ParserErrorText', + [System.Management.Automation.ErrorCategory]::ParserError, + @{ + Line = 1 + LineText = 'This is the line with the error' + EndColumn = 22 + } + ) + ) + } | Should -Throw -PassThru + + $actual = ($e | Out-String).TrimEnd() + $actual | Should -BeExactly $expected + } + + It 'Parser TargetObject converts StartColumn and EndColumn from string' { + $expected = (@( + ": " + "Line |" + " 5 | This is the line with the error" + " | ~~~~" + " | Test Parser Error" + ) -join ([Environment]::NewLine)).TrimEnd() + $e = { + [CmdletBinding()] + param () + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.Exception]::new('Test Parser Error'), + 'ParserErrorText', + [System.Management.Automation.ErrorCategory]::ParserError, + @{ + + Line = 5 + LineText = 'This is the line with the error' + StartColumn = "18" + EndColumn = "22" + } + ) + ) + } | Should -Throw -PassThru + + $actual = ($e | Out-String).TrimEnd() + $actual | Should -BeExactly $expected + } + + It 'Parser TargetObject ignores StartColumn if it cannot be converted' { + $expected = (@( + ": " + "Line |" + " 1 | This is the line with the error" + " | Test Parser Error" + ) -join ([Environment]::NewLine)).TrimEnd() + $e = { + [CmdletBinding()] + param () + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.Exception]::new('Test Parser Error'), + 'ParserErrorText', + [System.Management.Automation.ErrorCategory]::ParserError, + @{ + Line = 1 + LineText = 'This is the line with the error' + StartColumn = 'abc' + EndColumn = 22 + } + ) + ) + } | Should -Throw -PassThru + + $actual = ($e | Out-String).TrimEnd() + $actual | Should -BeExactly $expected + } + + It 'Parser TargetObject ignores EndColumn if it cannot be converted' { + $expected = (@( + ": " + "Line |" + " 5 | This is the line with the error" + " | ~~~~~~~~~~~~~~" + " | Test Parser Error" + ) -join ([Environment]::NewLine)).TrimEnd() + $e = { + [CmdletBinding()] + param () + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.Exception]::new('Test Parser Error'), + 'ParserErrorText', + [System.Management.Automation.ErrorCategory]::ParserError, + @{ + + Line = 5 + LineText = 'This is the line with the error' + StartColumn = 18 + EndColumn = 'abc' + } + ) + ) + } | Should -Throw -PassThru + + $actual = ($e | Out-String).TrimEnd() + $actual | Should -BeExactly $expected + } + + It 'Parser TargetObject ignores StartColumn with invalid value <Value>' -TestCases @( + @{ Value = -1 } + @{ Value = 0 } + @{ Value = 32 } # Beyond end of line + ) { + param ($Value) + + $expected = (@( + ": " + "Line |" + " 1 | This is the line with the error" + " | Test Parser Error" + ) -join ([Environment]::NewLine)).TrimEnd() + $e = { + [CmdletBinding()] + param () + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.Exception]::new('Test Parser Error'), + 'ParserErrorText', + [System.Management.Automation.ErrorCategory]::ParserError, + @{ + Line = 1 + LineText = 'This is the line with the error' + StartColumn = $Value + } + ) + ) + } | Should -Throw -PassThru + + $actual = ($e | Out-String).TrimEnd() + $actual | Should -BeExactly $expected + } + + It 'Parser TargetObject ignores EndColumn with invalid value <Value>' -TestCases @( + @{ Value = -1 } + @{ Value = 0 } + @{ Value = 17 } # Before StartColumn + @{ Value = 18 } # Equal to StartColumn + @{ Value = 33 } # Beyond end of line + 1 + ) { + param ($Value) + + $expected = (@( + ": " + "Line |" + " 1 | This is the line with the error" + " | ~~~~~~~~~~~~~~" + " | Test Parser Error" + ) -join ([Environment]::NewLine)).TrimEnd() + $e = { + [CmdletBinding()] + param () + + $PSCmdlet.ThrowTerminatingError( + [System.Management.Automation.ErrorRecord]::new( + [System.Exception]::new('Test Parser Error'), + 'ParserErrorText', + [System.Management.Automation.ErrorCategory]::ParserError, + @{ + Line = 1 + LineText = 'This is the line with the error' + StartColumn = 18 + EndColumn = $Value + } + ) + ) + } | Should -Throw -PassThru + + $actual = ($e | Out-String).TrimEnd() + $actual | Should -BeExactly $expected + } + It 'Exception thrown from Enumerator.MoveNext in a pipeline shows information' { $e = { $l = [System.Collections.Generic.List[string]] @('one', 'two') diff --git a/test/powershell/engine/Formatting/PSStyle.Tests.ps1 b/test/powershell/engine/Formatting/PSStyle.Tests.ps1 index d8a124a2332..18baed17dd2 100644 --- a/test/powershell/engine/Formatting/PSStyle.Tests.ps1 +++ b/test/powershell/engine/Formatting/PSStyle.Tests.ps1 @@ -488,7 +488,7 @@ Billy Bob… Senior DevOps … 13 $text.Trim().Replace("`r", "") | Should -BeExactly $expected.Replace("`r", "") } - It "Word wrapping for string with escape sequences" { + It "Word wrapping for string with escape sequences (1)" { $expected = @" `e[32;1mLongDescription : `e[0m`e[33mPowerShell `e[0m `e[33mscripting `e[0m @@ -501,7 +501,46 @@ Billy Bob… Senior DevOps … 13 $text.Trim().Replace("`r", "") | Should -BeExactly $expected.Replace("`r", "") } - It "Splitting multi-line string with escape sequences" { + It "Word wrapping for string with escape sequences (2)" { + $expected = @" +`e[32;1mLongDescription : `e[0m`e[33mPowerShell`e[0m + scripting + language +"@ + $obj = [pscustomobject] @{ LongDescription = "`e[33mPowerShell`e[0m scripting language" } + $obj | Format-List | Out-String -Width 35 | Out-File $outFile + + $text = Get-Content $outFile -Raw + $text.Trim().Replace("`r", "") | Should -BeExactly $expected.Replace("`r", "") + } + + It "Word wrapping for string with escape sequences (3)" { + $expected = @" +`e[32;1mLongDescription : `e[0m`e[33mPowerShell`e[0m + `e[32mscripting `e[0m + `e[32mlanguage`e[0m +"@ + $obj = [pscustomobject] @{ LongDescription = "`e[33mPowerShell`e[0m `e[32mscripting language" } + $obj | Format-List | Out-String -Width 35 | Out-File $outFile + + $text = Get-Content $outFile -Raw + $text.Trim().Replace("`r", "") | Should -BeExactly $expected.Replace("`r", "") + } + + It "Word wrapping for string with escape sequences (4)" { + $expected = @" +`e[32;1mLongDescription : `e[0m`e[33mPowerShell`e[0m + `e[32mscripting`e[0m + language +"@ + $obj = [pscustomobject] @{ LongDescription = "`e[33mPowerShell`e[0m `e[32mscripting`e[0m language" } + $obj | Format-List | Out-String -Width 35 | Out-File $outFile + + $text = Get-Content $outFile -Raw + $text.Trim().Replace("`r", "") | Should -BeExactly $expected.Replace("`r", "") + } + + It "Splitting multi-line string with escape sequences (1)" { $expected = @" `e[32;1mb : `e[0m`e[33mPowerShell is a task automation and configuration management program from Microsoft,`e[0m `e[33mconsisting of a command-line shell and the associated scripting language`e[0m @@ -513,6 +552,30 @@ Billy Bob… Senior DevOps … 13 $text.Trim().Replace("`r", "") | Should -BeExactly $expected.Replace("`r", "") } + It "Splitting multi-line string with escape sequences (2)" { + $expected = @" +`e[32;1mb : `e[0m`e[33mPowerShell is a task automation and configuration management program from Microsoft,`e[0m + consisting of a command-line shell and the associated scripting language +"@ + $obj = [pscustomobject] @{ b = "`e[33mPowerShell is a task automation and configuration management program from Microsoft,`e[0m`nconsisting of a command-line shell and the associated scripting language" } + $obj | Format-List | Out-File $outFile + + $text = Get-Content $outFile -Raw + $text.Trim().Replace("`r", "") | Should -BeExactly $expected.Replace("`r", "") + } + + It "Splitting multi-line string with escape sequences (3)" { + $expected = @" +`e[32;1mb : `e[0m`e[33mPowerShell is a task automation and configuration management program from Microsoft,`e[0m + `e[32mconsisting of a command-line shell and the associated scripting language`e[0m +"@ + $obj = [pscustomobject] @{ b = "`e[33mPowerShell is a task automation and configuration management program from Microsoft,`e[0m`n`e[32mconsisting of a command-line shell and the associated scripting language" } + $obj | Format-List | Out-File $outFile + + $text = Get-Content $outFile -Raw + $text.Trim().Replace("`r", "") | Should -BeExactly $expected.Replace("`r", "") + } + It "Wrapping long word with escape sequences" { $expected = @" `e[32;1mb : `e[0m`e[33mC:\repos\PowerShell\src\powershell-w`e[0m @@ -525,4 +588,18 @@ Billy Bob… Senior DevOps … 13 $text = Get-Content $outFile -Raw $text.Trim().Replace("`r", "") | Should -BeExactly $expected.Replace("`r", "") } + + It "Format 'MatchInfo' object correctly" { + $expected = @" +`e[32;1mb : `e[0mmouclass `e[7mMouse`e[0m Class Driver Mouse Class Driver Kernel Manual Running OK TRUE FALSE 12,288 `e[0m + 32,768 0 C:\WINDOWS\system32\drivers\mouclass.sys 4,096 +"@ + + ## This string mimics the VT decorated string for a 'MatchInfo' object that matches the word 'mouse'. + $str = "mouclass `e[7mMouse`e[0m Class Driver Mouse Class Driver Kernel Manual Running OK TRUE FALSE 12,288 32,768 0 C:\WINDOWS\system32\drivers\mouclass.sys 4,096" + $obj = [pscustomobject] @{ b = $str } + $text = $obj | Format-List | Out-String -Width 150 + + $text.Trim().Replace("`r", "") | Should -BeExactly $expected.Replace("`r", "") + } } diff --git a/test/powershell/engine/Help/UpdatableHelpSystem.Tests.ps1 b/test/powershell/engine/Help/UpdatableHelpSystem.Tests.ps1 index d4cbb420066..155654b3b80 100644 --- a/test/powershell/engine/Help/UpdatableHelpSystem.Tests.ps1 +++ b/test/powershell/engine/Help/UpdatableHelpSystem.Tests.ps1 @@ -191,7 +191,8 @@ function RunUpdateHelpTests param ( [string]$tag = "CI", [switch]$useSourcePath, - [switch]$userscope + [switch]$userscope, + [switch]$markAsPending ) foreach ($moduleName in $modulesInBox) @@ -214,12 +215,19 @@ function RunUpdateHelpTests It ('Validate Update-Help for module ''{0}'' in {1}' -F $moduleName, [PSCustomObject] $updateScope) -Skip:(!(Test-CanWriteToPsHome) -and $userscope -eq $false) { + if ($markAsPending -or ($IsLinux -and $moduleName -eq "PackageManagement")) { + Set-ItResult -Pending -Because "Update-Help from the web has intermittent connectivity issues. See issues #2807 and #6541." + return + } + # Delete the whole help directory - Remove-Item ($moduleHelpPath) -Recurse + if ($moduleHelpPath) { + Remove-Item ($moduleHelpPath) -Recurse -Force -ErrorAction SilentlyContinue + } [hashtable] $UICultureParam = $(if ((Get-UICulture).Name -ne $myUICulture) { @{ UICulture = $myUICulture } } else { @{} }) [hashtable] $sourcePathParam = $(if ($useSourcePath) { @{ SourcePath = Join-Path $PSScriptRoot assets } } else { @{} }) - Update-Help -Module:$moduleName -Force @UICultureParam @sourcePathParam -Scope:$updateScope + Update-Help -Module:$moduleName -Force @UICultureParam @sourcePathParam -Scope:$updateScope -ErrorAction Stop [hashtable] $userScopeParam = $(if ($userscope) { @{ UserScope = $true } } else { @{} }) ValidateInstalledHelpContent -moduleName:$moduleName @userScopeParam @@ -246,8 +254,15 @@ function RunSaveHelpTests { try { - $saveHelpFolder = Join-Path $TestDrive (Get-Random).ToString() - New-Item $saveHelpFolder -Force -ItemType Directory > $null + $saveHelpFolder = if ($TestDrive) { + Join-Path $TestDrive (Get-Random).ToString() + } else { + $null + } + + if ($saveHelpFolder) { + New-Item $saveHelpFolder -Force -ItemType Directory > $null + } ## Save help has intermittent connectivity issues for downloading PackageManagement help content. ## Hence the test has been marked as Pending. @@ -283,7 +298,9 @@ function RunSaveHelpTests } finally { - Remove-Item $saveHelpFolder -Force -ErrorAction SilentlyContinue -Recurse + if ($saveHelpFolder) { + Remove-Item $saveHelpFolder -Force -ErrorAction SilentlyContinue -Recurse + } } } } diff --git a/test/powershell/engine/Remoting/PSSession.Tests.ps1 b/test/powershell/engine/Remoting/PSSession.Tests.ps1 index cbf7313eed0..317ed4af734 100644 --- a/test/powershell/engine/Remoting/PSSession.Tests.ps1 +++ b/test/powershell/engine/Remoting/PSSession.Tests.ps1 @@ -5,6 +5,9 @@ # PSSession tests for non-Windows platforms # +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] +param() + function GetRandomString() { return [System.IO.Path]::GetFileNameWithoutExtension([System.IO.Path]::GetRandomFileName()) @@ -88,3 +91,48 @@ Describe "SkipCACheck and SkipCNCheck PSSession options are required for New-PSS $er.Exception.ErrorCode | Should -Be $expectedErrorCode } } + +Describe "New-PSSession -UseWindowsPowerShell switch parameter" -Tag "CI" { + + BeforeAll { + $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + + if (-not $IsWindows) { + $PSDefaultParameterValues['it:skip'] = $true + } + } + + AfterAll { + $global:PSDefaultParameterValues = $originalDefaultParameterValues + } + + It "Should respect explicit -UseWindowsPowerShell:`$false parameter value" { + # Test 1: -UseWindowsPowerShell:$true should create a Windows PowerShell 5.1 session + $sessionWithTrue = $null + try { + { $script:sessionWithTrue = New-PSSession -UseWindowsPowerShell:$true } | Should -Not -Throw + $script:sessionWithTrue | Should -Not -BeNullOrEmpty + + # Verify it's Windows PowerShell 5.1 + $version = Invoke-Command -Session $script:sessionWithTrue -ScriptBlock { $PSVersionTable.PSVersion } + $version.Major | Should -Be 5 + $version.Minor | Should -Be 1 + } + finally { + if ($script:sessionWithTrue) { Remove-PSSession $script:sessionWithTrue -ErrorAction SilentlyContinue } + } + + # Test 2: -UseWindowsPowerShell:$false should use WSMan transport (not Process) + $sessionWithFalse = $null + try { + { $script:sessionWithFalse = New-PSSession -UseWindowsPowerShell:$false } | Should -Not -Throw + $script:sessionWithFalse | Should -Not -BeNullOrEmpty + + # Transport should be WSMan, not Process + $script:sessionWithFalse.Transport | Should -Be 'WSMan' + } + finally { + if ($script:sessionWithFalse) { Remove-PSSession $script:sessionWithFalse -ErrorAction SilentlyContinue } + } + } +} diff --git a/test/powershell/engine/Remoting/RemoteSession.Basic.Tests.ps1 b/test/powershell/engine/Remoting/RemoteSession.Basic.Tests.ps1 index 43f92cef295..2093eaa7ce5 100644 --- a/test/powershell/engine/Remoting/RemoteSession.Basic.Tests.ps1 +++ b/test/powershell/engine/Remoting/RemoteSession.Basic.Tests.ps1 @@ -1,6 +1,9 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] +param() + Import-Module HelpersCommon function GetRandomString() @@ -80,9 +83,7 @@ Describe "JEA session Transcript script test" -Tag @("Feature", 'RequireAdminOnW AfterAll { if ($skipTest) { Pop-DefaultParameterValueStack - return } - Pop-DefaultParameterValueStack } It "Configuration name should be in the transcript header" { diff --git a/test/powershell/engine/Remoting/SSHRemotingCmdlets.Tests.ps1 b/test/powershell/engine/Remoting/SSHRemotingCmdlets.Tests.ps1 index 1568d284d0f..c1090bdc186 100644 --- a/test/powershell/engine/Remoting/SSHRemotingCmdlets.Tests.ps1 +++ b/test/powershell/engine/Remoting/SSHRemotingCmdlets.Tests.ps1 @@ -70,3 +70,24 @@ Describe "SSHConnection parameter hashtable type conversions" -Tags 'Feature', ' $err.FullyQualifiedErrorId | Should -Match 'PSSessionOpenFailed' } } + +Describe "No hangs when host doesn't exist" -Tags "CI" { + $testCases = @( + @{ + Name = 'Verifies no hang for New-PSSession with non-existing host name' + ScriptBlock = { New-PSSession -HostName "test-notexist" -UserName "test" -ErrorAction Stop } + FullyQualifiedErrorId = 'PSSessionOpenFailed' + }, + @{ + Name = 'Verifies no hang for Invoke-Command with non-existing host name' + ScriptBlock = { Invoke-Command -HostName "test-notexist" -UserName "test" -ScriptBlock { 1 } -ErrorAction Stop } + FullyQualifiedErrorId = 'PSSessionStateBroken' + } + ) + + It "<Name>" -TestCases $testCases { + param ($ScriptBlock, $FullyQualifiedErrorId) + + $ScriptBlock | Should -Throw -ErrorId $FullyQualifiedErrorId -ExceptionType 'System.Management.Automation.Remoting.PSRemotingTransportException' + } +} diff --git a/test/powershell/engine/Remoting/SessionOption.Tests.ps1 b/test/powershell/engine/Remoting/SessionOption.Tests.ps1 index 1ff51e2a707..e85777e90ba 100644 --- a/test/powershell/engine/Remoting/SessionOption.Tests.ps1 +++ b/test/powershell/engine/Remoting/SessionOption.Tests.ps1 @@ -1,6 +1,7 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. try { + $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() if ( ! $IsWindows ) { $PSDefaultParameterValues['it:skip'] = $true } @@ -52,5 +53,5 @@ try { } } finally { - $PSDefaultParameterValues.remove("it:skip") + $global:PSDefaultParameterValues = $originalDefaultParameterValues } diff --git a/test/powershell/engine/Security/FileSignature.Tests.ps1 b/test/powershell/engine/Security/FileSignature.Tests.ps1 index 51194903a94..f2815fad4ff 100644 --- a/test/powershell/engine/Security/FileSignature.Tests.ps1 +++ b/test/powershell/engine/Security/FileSignature.Tests.ps1 @@ -18,6 +18,9 @@ Describe "Windows platform file signatures" -Tags 'Feature' { $signature | Should -Not -BeNullOrEmpty $signature.Status | Should -BeExactly 'Valid' $signature.SignatureType | Should -BeExactly 'Catalog' + + # Verify that SubjectAlternativeName property exists + $signature.PSObject.Properties.Name | Should -Contain 'SubjectAlternativeName' } } @@ -89,6 +92,7 @@ Describe "Windows file content signatures" -Tags @('Feature', 'RequireAdminOnWin AfterAll { if ($shouldSkip) { + Pop-DefaultParameterValueStack return } @@ -185,4 +189,102 @@ Describe "Windows file content signatures" -Tags @('Feature', 'RequireAdminOnWin $actual.SignerCertificate.Thumbprint | Should -Be $certificate.Thumbprint $actual.Status | Should -Be 'Valid' } + + It "Verifies SubjectAlternativeName is populated for certificate with SAN" { + $session = New-PSSession -UseWindowsPowerShell + try { + $sanThumbprint = Invoke-Command -Session $session -ScriptBlock { + $testPrefix = 'SelfSignedTestSAN' + + $enhancedKeyUsage = [Security.Cryptography.OidCollection]::new() + $null = $enhancedKeyUsage.Add('1.3.6.1.5.5.7.3.3') + + $caParams = @{ + Extension = @( + [Security.Cryptography.X509Certificates.X509BasicConstraintsExtension]::new($true, $false, 0, $true), + [Security.Cryptography.X509Certificates.X509KeyUsageExtension]::new('KeyCertSign', $false), + [Security.Cryptography.X509Certificates.X509EnhancedKeyUsageExtension]::new($enhancedKeyUsage, $false) + ) + CertStoreLocation = 'Cert:\CurrentUser\My' + NotAfter = (Get-Date).AddDays(1) + Type = 'Custom' + } + $sanCA = PKI\New-SelfSignedCertificate @caParams -Subject "CN=$testPrefix-CA" + + $rootStore = Get-Item -Path Cert:\LocalMachine\Root + $rootStore.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) + try { + $rootStore.Add([System.Security.Cryptography.X509Certificates.X509Certificate2]::new($sanCA.RawData)) + } finally { + $rootStore.Close() + } + + $certParams = @{ + CertStoreLocation = 'Cert:\CurrentUser\My' + KeyUsage = 'DigitalSignature' + TextExtension = @( + "2.5.29.37={text}1.3.6.1.5.5.7.3.3", + "2.5.29.19={text}", + "2.5.29.17={text}DNS=test.example.com&DNS=*.example.com" + ) + Type = 'Custom' + } + $sanCert = PKI\New-SelfSignedCertificate @certParams -Subject "CN=$testPrefix-Signed" -Signer $sanCA + + $publisherStore = Get-Item -Path Cert:\LocalMachine\TrustedPublisher + $publisherStore.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) + try { + $publisherStore.Add([System.Security.Cryptography.X509Certificates.X509Certificate2]::new($sanCert.RawData)) + } finally { + $publisherStore.Close() + } + + $sanCA | Remove-Item + $sanCA.Thumbprint, $sanCert.Thumbprint + } + } finally { + $session | Remove-PSSession + } + + $sanCARootThumbprint = $sanThumbprint[0] + $sanCertThumbprint = $sanThumbprint[1] + $sanCertificate = Get-Item -Path Cert:\CurrentUser\My\$sanCertThumbprint + + try { + Set-Content -Path testdrive:\test.ps1 -Value 'Write-Output "Test SAN"' -Encoding UTF8NoBOM + + $scriptPath = Join-Path $TestDrive test.ps1 + $status = Set-AuthenticodeSignature -FilePath $scriptPath -Certificate $sanCertificate + $status.Status | Should -Be 'Valid' + + $actual = Get-AuthenticodeSignature -FilePath $scriptPath + $actual.SubjectAlternativeName | Should -Not -BeNullOrEmpty + ,$actual.SubjectAlternativeName | Should -BeOfType [string[]] + $actual.SubjectAlternativeName.Count | Should -Be 2 + $actual.SubjectAlternativeName[0] | Should -BeExactly 'DNS Name=test.example.com' + $actual.SubjectAlternativeName[1] | Should -BeExactly 'DNS Name=*.example.com' + } finally { + Remove-Item -Path "Cert:\LocalMachine\Root\$sanCARootThumbprint" -Force -ErrorAction Ignore + Remove-Item -Path "Cert:\LocalMachine\TrustedPublisher\$sanCertThumbprint" -Force -ErrorAction Ignore + Remove-Item -Path "Cert:\CurrentUser\My\$sanCertThumbprint" -Force -ErrorAction Ignore + } + } + + It "Verifies SubjectAlternativeName is null when certificate has no SAN" { + Set-Content -Path testdrive:\test.ps1 -Value 'Write-Output "Test No SAN"' -Encoding UTF8NoBOM + + $scriptPath = Join-Path $TestDrive test.ps1 + $status = Set-AuthenticodeSignature -FilePath $scriptPath -Certificate $certificate + $status.Status | Should -Be 'Valid' + + $actual = Get-AuthenticodeSignature -FilePath $scriptPath + $actual.SignerCertificate.Thumbprint | Should -Be $certificate.Thumbprint + $actual.Status | Should -Be 'Valid' + + # Verify that SubjectAlternativeName property exists + $actual.PSObject.Properties.Name | Should -Contain 'SubjectAlternativeName' + + # Verify the content is null when certificate has no SAN extension + $actual.SubjectAlternativeName | Should -BeNullOrEmpty + } } diff --git a/test/powershell/engine/WildcardPattern.Tests.ps1 b/test/powershell/engine/WildcardPattern.Tests.ps1 new file mode 100644 index 00000000000..81ced10f253 --- /dev/null +++ b/test/powershell/engine/WildcardPattern.Tests.ps1 @@ -0,0 +1,77 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe "WildcardPattern.ToRegex Tests" -Tags "CI" { + It "Converts '<Pattern>' to regex pattern '<Expected>'" -TestCases @( + @{ Pattern = '*.txt'; Expected = '\.txt$' } + @{ Pattern = 'test?.log'; Expected = '^test.\.log$' } + @{ Pattern = 'file[0-9].txt'; Expected = '^file[0-9]\.txt$' } + @{ Pattern = 'test.log'; Expected = '^test\.log$' } + @{ Pattern = '*test*file*.txt'; Expected = 'test.*file.*\.txt$' } + @{ Pattern = 'file[0-9][a-z].txt'; Expected = '^file[0-9][a-z]\.txt$' } + @{ Pattern = 'test*'; Expected = '^test' } + @{ Pattern = '*test*'; Expected = 'test' } + ) { + param($Pattern, $Expected) + $wildcardPattern = [System.Management.Automation.WildcardPattern]::new($Pattern) + $regex = $wildcardPattern.ToRegex() + $regex | Should -BeOfType ([regex]) + $regex.ToString() | Should -BeExactly $Expected + } + + It "Converts '<Pattern>' with <OptionName> option" -TestCases @( + @{ Pattern = 'TEST'; OptionName = 'IgnoreCase'; Option = [System.Management.Automation.WildcardOptions]::IgnoreCase; Expected = '^TEST$'; ExpectedRegexOptions = 'IgnoreCase, Singleline'; TestString = 'test'; ExpectedMatch = $true } + @{ Pattern = 'test'; OptionName = 'CultureInvariant'; Option = [System.Management.Automation.WildcardOptions]::CultureInvariant; Expected = '^test$'; ExpectedRegexOptions = 'Singleline, CultureInvariant'; TestString = 'test'; ExpectedMatch = $true } + @{ Pattern = 'test*'; OptionName = 'Compiled'; Option = [System.Management.Automation.WildcardOptions]::Compiled; Expected = '^test'; ExpectedRegexOptions = 'Compiled, Singleline'; TestString = 'testing'; ExpectedMatch = $true } + ) { + param($Pattern, $OptionName, $Option, $Expected, $ExpectedRegexOptions, $TestString, $ExpectedMatch) + $wildcardPattern = [System.Management.Automation.WildcardPattern]::new($Pattern, $Option) + $regex = $wildcardPattern.ToRegex() + $regex | Should -BeOfType ([regex]) + $regex.ToString() | Should -BeExactly $Expected + $regex.Options.ToString() | Should -BeExactly $ExpectedRegexOptions + $regex.IsMatch($TestString) | Should -Be $ExpectedMatch + } + + It "Regex from '<Pattern>' matches '<TestString>': <ShouldMatch>" -TestCases @( + @{ Pattern = '*test*file*.txt'; TestString = 'mytestmyfile123.txt'; ShouldMatch = $true } + @{ Pattern = 'file[0-9][a-z].txt'; TestString = 'file5a.txt'; ShouldMatch = $true } + @{ Pattern = 'file[0-9][a-z].txt'; TestString = 'file55.txt'; ShouldMatch = $false } + ) { + param($Pattern, $TestString, $ShouldMatch) + $regex = [System.Management.Automation.WildcardPattern]::new($Pattern).ToRegex() + $regex.IsMatch($TestString) | Should -Be $ShouldMatch + } + + Context "Edge cases" { + It "Handles empty pattern" { + $pattern = [System.Management.Automation.WildcardPattern]::new("") + $regex = $pattern.ToRegex() + $regex | Should -BeOfType ([regex]) + $regex.ToString() | Should -Be "^$" + } + + It "Handles pattern with only asterisk" { + $pattern = [System.Management.Automation.WildcardPattern]::new("*") + $regex = $pattern.ToRegex() + $regex | Should -BeOfType ([regex]) + $regex.ToString() | Should -BeExactly "" + $regex.IsMatch("anything") | Should -BeTrue + $regex.IsMatch("") | Should -BeTrue + } + + It "Handles escaped '<Char>' wildcard character" -TestCases @( + @{ Char = '*'; Pattern = 'file`*.txt'; Expected = '^file\*\.txt$' } + @{ Char = '?'; Pattern = 'file`?.txt'; Expected = '^file\?\.txt$' } + @{ Char = '['; Pattern = 'file`[.txt'; Expected = '^file\[\.txt$' } + @{ Char = ']'; Pattern = 'file`].txt'; Expected = '^file]\.txt$' } + ) { + param($Char, $Pattern, $Expected) + $wildcardPattern = [System.Management.Automation.WildcardPattern]::new($Pattern) + $regex = $wildcardPattern.ToRegex() + $regex | Should -BeOfType ([regex]) + $regex.ToString() | Should -BeExactly $Expected + } + + } +} diff --git a/test/tools/Modules/HelpersCommon/HelpersCommon.psm1 b/test/tools/Modules/HelpersCommon/HelpersCommon.psm1 index 5c05198ff8a..02588b612db 100644 --- a/test/tools/Modules/HelpersCommon/HelpersCommon.psm1 +++ b/test/tools/Modules/HelpersCommon/HelpersCommon.psm1 @@ -69,7 +69,7 @@ function Get-RandomFileName $SCRIPT:TesthookType = [system.management.automation.internal.internaltesthooks] function Test-TesthookIsSet { - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingEmptyCatchBlock", '')] # , Justification = "an error message is not appropriate for this function")] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingEmptyCatchBlock", '')] # , Justification = "an error message is not appropriate for this function")] param ( [ValidateNotNullOrEmpty()] [Parameter(Mandatory=$true)] @@ -197,7 +197,7 @@ public class TestDynamic : DynamicObject # Upload an artifact in VSTS # On other systems will just log where the file was placed function Send-VstsLogFile { - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = "needed for VSO")] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = "needed for VSO")] param ( [parameter(Mandatory,ParameterSetName='contents')] [string[]] @@ -324,8 +324,8 @@ function New-RandomHexString $script:CanWriteToPsHome = $null function Test-CanWriteToPsHome { - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingEmptyCatchBlock', '', Justification = "an error message is not appropriate for this function")] - param () + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingEmptyCatchBlock', '', Justification = "an error message is not appropriate for this function")] + param () if ($null -ne $script:CanWriteToPsHome) { return $script:CanWriteToPsHome } @@ -476,20 +476,20 @@ function Test-IsWindows2016 { # Ensure that the global:PSDefaultParameterValues variable is a hashtable function Initialize-PSDefaultParameterValue { - if ( $global:PSDefaultParameterValues -isnot [hashtable] ) { - $global:PSDefaultParameterValues = @{} - } + if ( $global:PSDefaultParameterValues -isnot [hashtable] ) { + $global:PSDefaultParameterValues = @{} + } } # reset the stack function Reset-DefaultParameterValueStack { - $script:DefaultParameterValueStack = [system.collections.generic.Stack[hashtable]]::new() + $script:DefaultParameterValueStack = [system.collections.generic.Stack[hashtable]]::new() Initialize-PSDefaultParameterValue } # return the current stack function Get-DefaultParameterValueStack { - $script:DefaultParameterValueStack + $script:DefaultParameterValueStack } # PSDefaultParameterValue may not have both skip and pending keys @@ -507,35 +507,35 @@ function Test-PSDefaultParameterValue { # if $ht is null, then the current value of $global:PSDefaultParameterValues is pushed # if $NewValue is used, then $ht is used as the new value of $global:PSDefaultParameterValues function Push-DefaultParameterValueStack { - param ([hashtable]$ht, [switch]$NewValue) + param ([hashtable]$ht, [switch]$NewValue) Initialize-PSDefaultParameterValue - $script:DefaultParameterValueStack.Push($global:PSDefaultParameterValues.Clone()) - if ( $ht ) { - if ( $NewValue ) { - $global:PSDefaultParameterValues = $ht - } - else { - foreach ($k in $ht.Keys) { - $global:PSDefaultParameterValues[$k] = $ht[$k] - } - } + $script:DefaultParameterValueStack.Push($global:PSDefaultParameterValues.Clone()) + if ( $ht ) { + if ( $NewValue ) { + $global:PSDefaultParameterValues = $ht + } + else { + foreach ($k in $ht.Keys) { + $global:PSDefaultParameterValues[$k] = $ht[$k] + } + } if ( ! (Test-PSDefaultParameterValue)) { Write-Warning -Message "PSDefaultParameterValues may not have both skip and pending keys, resetting." Pop-DefaultParameterValueStack } - } + } } function Pop-DefaultParameterValueStack { - try { - $global:PSDefaultParameterValues = $script:DefaultParameterValueStack.Pop() - return $true - } - catch { + try { + $global:PSDefaultParameterValues = $script:DefaultParameterValueStack.Pop() + return $true + } + catch { Initialize-PSDefaultParameterValue - return $false - } + return $false + } } function Get-HelpNetworkTestCases diff --git a/test/tools/Modules/Microsoft.PowerShell.RemotingTools/Microsoft.PowerShell.RemotingTools.psm1 b/test/tools/Modules/Microsoft.PowerShell.RemotingTools/Microsoft.PowerShell.RemotingTools.psm1 index 41a57772fd3..911aa8da0ce 100644 --- a/test/tools/Modules/Microsoft.PowerShell.RemotingTools/Microsoft.PowerShell.RemotingTools.psm1 +++ b/test/tools/Modules/Microsoft.PowerShell.RemotingTools/Microsoft.PowerShell.RemotingTools.psm1 @@ -321,7 +321,7 @@ $typeDef = @' .Parameter PowerShellFilePath Specifies the file path to the PowerShell command used to host the SSH remoting PowerShell endpoint. If no value is specified then the currently running PowerShell executable path is used - in the subsytem command. + in the subsystem command. .Parameter Force When true, this cmdlet will update the sshd_config configuration file without prompting. #> diff --git a/test/tools/Modules/WebListener/WebListener.psm1 b/test/tools/Modules/WebListener/WebListener.psm1 index 15ce1d8fe38..7f590b79b76 100644 --- a/test/tools/Modules/WebListener/WebListener.psm1 +++ b/test/tools/Modules/WebListener/WebListener.psm1 @@ -1,6 +1,10 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')] +param() + Class WebListener { [int]$HttpPort diff --git a/test/tools/NamedPipeConnection/build.ps1 b/test/tools/NamedPipeConnection/build.ps1 index a0978c4cb34..3d92df01fcd 100644 --- a/test/tools/NamedPipeConnection/build.ps1 +++ b/test/tools/NamedPipeConnection/build.ps1 @@ -36,8 +36,8 @@ param ( [ValidateSet("Debug", "Release")] [string] $BuildConfiguration = "Debug", - [ValidateSet("net10.0")] - [string] $BuildFramework = "net10.0" + [ValidateSet("net11.0")] + [string] $BuildFramework = "net11.0" ) $script:ModuleName = 'Microsoft.PowerShell.NamedPipeConnection' diff --git a/test/tools/NamedPipeConnection/src/code/Microsoft.PowerShell.NamedPipeConnection.csproj b/test/tools/NamedPipeConnection/src/code/Microsoft.PowerShell.NamedPipeConnection.csproj index 89147481bc3..ba216dee23e 100644 --- a/test/tools/NamedPipeConnection/src/code/Microsoft.PowerShell.NamedPipeConnection.csproj +++ b/test/tools/NamedPipeConnection/src/code/Microsoft.PowerShell.NamedPipeConnection.csproj @@ -8,9 +8,9 @@ <AssemblyVersion>1.0.0.0</AssemblyVersion> <FileVersion>1.0.0</FileVersion> <InformationalVersion>1.0.0</InformationalVersion> - <TargetFrameworks>net10.0</TargetFrameworks> + <TargetFrameworks>net11.0</TargetFrameworks> <SuppressNETCoreSdkPreviewMessage>true</SuppressNETCoreSdkPreviewMessage> - <LangVersion>13.0</LangVersion> + <LangVersion>preview</LangVersion> </PropertyGroup> <ItemGroup> diff --git a/test/tools/OpenCover/OpenCover.psm1 b/test/tools/OpenCover/OpenCover.psm1 index 9e7adb640ca..e8848a15085 100644 --- a/test/tools/OpenCover/OpenCover.psm1 +++ b/test/tools/OpenCover/OpenCover.psm1 @@ -624,7 +624,7 @@ function Invoke-OpenCover [parameter()]$OutputLog = "$HOME/Documents/OpenCover.xml", [parameter()]$TestPath = "${script:psRepoPath}/test/powershell", [parameter()]$OpenCoverPath = "$HOME/OpenCover", - [parameter()]$PowerShellExeDirectory = "${script:psRepoPath}/src/powershell-win-core/bin/CodeCoverage/net10.0/win7-x64/publish", + [parameter()]$PowerShellExeDirectory = "${script:psRepoPath}/src/powershell-win-core/bin/CodeCoverage/net11.0/win7-x64/publish", [parameter()]$PesterLogElevated = "$HOME/Documents/TestResultsElevated.xml", [parameter()]$PesterLogUnelevated = "$HOME/Documents/TestResultsUnelevated.xml", [parameter()]$PesterLogFormat = "NUnitXml", diff --git a/test/tools/TestAlc/init/Init.cs b/test/tools/TestAlc/init/Init.cs index 4241e56fa4e..33f6635f712 100644 --- a/test/tools/TestAlc/init/Init.cs +++ b/test/tools/TestAlc/init/Init.cs @@ -10,7 +10,7 @@ namespace Test.Isolated.Init { - internal class CustomLoadContext : AssemblyLoadContext + internal sealed class CustomLoadContext : AssemblyLoadContext { private readonly string _dependencyDirPath; diff --git a/test/tools/TestAlc/nested/Test.Isolated.Nested.csproj b/test/tools/TestAlc/nested/Test.Isolated.Nested.csproj index 8777dd056de..886b5f11826 100644 --- a/test/tools/TestAlc/nested/Test.Isolated.Nested.csproj +++ b/test/tools/TestAlc/nested/Test.Isolated.Nested.csproj @@ -19,7 +19,7 @@ <ItemGroup> <PackageReference Include="PowerShellStandard.Library" Version="5.1.1" PrivateAssets="All" /> - <PackageReference Include="System.CommandLine" Version="2.0.0-beta6.25358.103" /> + <PackageReference Include="System.CommandLine" Version="2.0.0" /> </ItemGroup> </Project> diff --git a/test/tools/TestExe/TestExe.cs b/test/tools/TestExe/TestExe.cs index 39c1d2b2ecb..9230f9e6bff 100644 --- a/test/tools/TestExe/TestExe.cs +++ b/test/tools/TestExe/TestExe.cs @@ -9,10 +9,18 @@ using System.IO; using System.Globalization; using System.Linq; +using Microsoft.Win32; namespace TestExe { - internal class TestExe + [Flags] + internal enum EnvTarget + { + User = 1, + System = 2, + } + + internal sealed class TestExe { private static int Main(string[] args) { @@ -47,6 +55,15 @@ private static int Main(string[] args) case "-writebytes": WriteBytes(args.AsSpan()[1..]); break; + case "-updateuserpath": + UpdateEnvPath(EnvTarget.User); + break; + case "-updatesystempath": + UpdateEnvPath(EnvTarget.System); + break; + case "-updateuserandsystempath": + UpdateEnvPath(EnvTarget.User | EnvTarget.System); + break; case "--help": case "-h": PrintHelp(); @@ -154,7 +171,7 @@ private static void CreateChildProcess(string[] args) { if (args.Length > 1) { - uint num = UInt32.Parse(args[1]); + uint num = uint.Parse(args[1]); for (uint i = 0; i < num; i++) { Process child = new Process(); @@ -166,6 +183,61 @@ private static void CreateChildProcess(string[] args) // sleep is needed so the process doesn't exit before the test case kill it Thread.Sleep(100000); } + + private static void UpdateEnvPath(EnvTarget target) + { + if (!OperatingSystem.IsWindows()) + { + return; + } + + const string EnvVarName = "Path"; + const string UserEnvRegPath = "Environment"; + const string SysEnvRegPath = @"SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; + + if (target.HasFlag(EnvTarget.User)) + { + // Append to the User Path. + using RegistryKey reg = Registry.CurrentUser.OpenSubKey(UserEnvRegPath, writable: true); + UpdateEnvPathImpl(reg, append: true, @"X:\not-exist-user-path"); + } + + if (target.HasFlag(EnvTarget.System)) + { + // Prepend to the System Path. + using RegistryKey reg = Registry.LocalMachine.OpenSubKey(SysEnvRegPath, writable: true); + UpdateEnvPathImpl(reg, append: false, @"X:\not-exist-sys-path"); + } + + static void UpdateEnvPathImpl(RegistryKey regKey, bool append, string newPathItem) + { + // Get the registry value kind. + RegistryValueKind kind = regKey.GetValueKind(EnvVarName); + + // Get the literal registry value (not expanded) for the env var. + string oldValue = (string)regKey.GetValue( + EnvVarName, + defaultValue: string.Empty, + RegistryValueOptions.DoNotExpandEnvironmentNames); + + string newValue; + if (append) + { + // Append to the old value. + string separator = (oldValue is "" || oldValue.EndsWith(';')) ? string.Empty : ";"; + newValue = $"{oldValue}{separator}{newPathItem}"; + } + else + { + // Prepend to the old value. + string separator = (oldValue is "" || oldValue.StartsWith(';')) ? string.Empty : ";"; + newValue = $"{newPathItem}{separator}{oldValue}"; + } + + // Set the new value and preserve the original value kind. + regKey.SetValue(EnvVarName, newValue, kind); + } + } } internal static partial class Interop diff --git a/test/tools/TestMetadata.json b/test/tools/TestMetadata.json index bd716ccec7b..19ffb93ce92 100644 --- a/test/tools/TestMetadata.json +++ b/test/tools/TestMetadata.json @@ -1,9 +1,6 @@ { "ExperimentalFeatures": { "ExpTest.FeatureOne": [ "test/powershell/engine/ExperimentalFeature/ExperimentalFeature.Basic.Tests.ps1" ], - "PSCultureInvariantReplaceOperator": [ "test/powershell/Language/Operators/ReplaceOperator.Tests.ps1" ], - "Microsoft.PowerShell.Utility.PSManageBreakpointsInRunspace": [ "test/powershell/Modules/Microsoft.PowerShell.Utility/RunspaceBreakpointManagement.Tests.ps1" ], - "PSNativeWindowsTildeExpansion": [ "test/powershell/Language/Scripting/NativeExecution/NativeWindowsTildeExpansion.Tests.ps1" ], "PSSerializeJSONLongEnumAsNumber": [ "test/powershell/Modules/Microsoft.PowerShell.Utility/ConvertTo-Json.PSSerializeJSONLongEnumAsNumber.Tests.ps1" ] } } diff --git a/test/tools/TestService/TestService.csproj b/test/tools/TestService/TestService.csproj index c04e4df08bf..37b73426f6c 100644 --- a/test/tools/TestService/TestService.csproj +++ b/test/tools/TestService/TestService.csproj @@ -15,8 +15,8 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.Windows.Compatibility" Version="10.0.0-preview.6.25358.103" /> - <PackageReference Include="System.Data.SqlClient" Version="4.9.0" /> + <PackageReference Include="Microsoft.Windows.Compatibility" Version="11.0.0-preview.3.26207.106" /> + <PackageReference Include="System.Data.SqlClient" Version="4.9.1" /> </ItemGroup> </Project> diff --git a/test/tools/WebListener/Program.cs b/test/tools/WebListener/Program.cs index 500e1c7062c..58baf77b1d7 100644 --- a/test/tools/WebListener/Program.cs +++ b/test/tools/WebListener/Program.cs @@ -6,9 +6,9 @@ using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; -using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Server.Kestrel.Https; +using Microsoft.Extensions.Hosting; namespace mvc { @@ -18,108 +18,112 @@ public static void Main(string[] args) { if (args.Length != 7) { - System.Console.WriteLine("Required: <CertificatePath> <CertificatePassword> <HTTPPortNumber> <HTTPSPortNumberTls12> <HTTPSPortNumberTls11> <HTTPSPortNumberTls> <HTTPSPortNumberTls12>"); + Console.WriteLine("Required: <CertificatePath> <CertificatePassword> <HTTPPortNumber> <HTTPSPortNumberTls12> <HTTPSPortNumberTls11> <HTTPSPortNumberTls> <HTTPSPortNumberTls12>"); Environment.Exit(1); } - BuildWebHost(args).Run(); + BuildHost(args).Run(); } - public static IWebHost BuildWebHost(string[] args) => - WebHost.CreateDefaultBuilder() - .UseStartup<Startup>().UseKestrel(options => + public static IHost BuildHost(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => { - options.AllowSynchronousIO = true; - - options.Listen( - IPAddress.Loopback, - int.Parse(args[2]), - listenOptions => - { - listenOptions.Protocols = Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols.Http1AndHttp2; - }); - - options.Listen( - IPAddress.Loopback, - int.Parse(args[3]), - listenOptions => - { - #pragma warning disable SYSLIB0057 - var certificate = new X509Certificate2(args[0], args[1]); - #pragma warning restore SYSLIB0057 - - HttpsConnectionAdapterOptions httpsOption = new HttpsConnectionAdapterOptions(); - httpsOption.SslProtocols = SslProtocols.Tls12; - httpsOption.ClientCertificateMode = ClientCertificateMode.AllowCertificate; - httpsOption.ClientCertificateValidation = (inCertificate, inChain, inPolicy) => { return true; }; - httpsOption.CheckCertificateRevocation = false; - httpsOption.ServerCertificate = certificate; - listenOptions.UseHttps(httpsOption); - listenOptions.Protocols = Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols.Http1AndHttp2; - }); - - options.Listen( - IPAddress.Loopback, - int.Parse(args[4]), - listenOptions => - { - #pragma warning disable SYSLIB0057 - var certificate = new X509Certificate2(args[0], args[1]); - #pragma warning restore SYSLIB0057 - - HttpsConnectionAdapterOptions httpsOption = new HttpsConnectionAdapterOptions(); - - // TLS 1.1 is obsolete. Using this value now defaults to TLS 1.2. - httpsOption.SslProtocols = SslProtocols.Tls12; - - httpsOption.ClientCertificateMode = ClientCertificateMode.AllowCertificate; - httpsOption.ClientCertificateValidation = (inCertificate, inChain, inPolicy) => { return true; }; - httpsOption.CheckCertificateRevocation = false; - httpsOption.ServerCertificate = certificate; - listenOptions.UseHttps(httpsOption); - listenOptions.Protocols = Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols.Http1AndHttp2; - }); - - options.Listen( - IPAddress.Loopback, - int.Parse(args[5]), - listenOptions => - { - #pragma warning disable SYSLIB0057 - var certificate = new X509Certificate2(args[0], args[1]); - #pragma warning restore SYSLIB0057 - - HttpsConnectionAdapterOptions httpsOption = new HttpsConnectionAdapterOptions(); - - // TLS is obsolete. Using this value now defaults to TLS 1.2. - httpsOption.SslProtocols = SslProtocols.Tls12; - - httpsOption.ClientCertificateMode = ClientCertificateMode.AllowCertificate; - httpsOption.ClientCertificateValidation = (inCertificate, inChain, inPolicy) => { return true; }; - httpsOption.CheckCertificateRevocation = false; - httpsOption.ServerCertificate = certificate; - listenOptions.UseHttps(httpsOption); - listenOptions.Protocols = Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols.Http1AndHttp2; - }); - - options.Listen( - IPAddress.Loopback, - int.Parse(args[6]), - listenOptions => - { - #pragma warning disable SYSLIB0057 - var certificate = new X509Certificate2(args[0], args[1]); - #pragma warning restore SYSLIB0057 - - HttpsConnectionAdapterOptions httpsOption = new HttpsConnectionAdapterOptions(); - httpsOption.SslProtocols = SslProtocols.Tls13; - httpsOption.ClientCertificateMode = ClientCertificateMode.AllowCertificate; - httpsOption.ClientCertificateValidation = (inCertificate, inChain, inPolicy) => { return true; }; - httpsOption.CheckCertificateRevocation = false; - httpsOption.ServerCertificate = certificate; - listenOptions.UseHttps(httpsOption); - listenOptions.Protocols = Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols.Http1AndHttp2; - }); + webBuilder.UseStartup<Startup>(); + webBuilder.UseKestrel(options => + { + options.AllowSynchronousIO = true; + + options.Listen( + IPAddress.Loopback, + int.Parse(args[2]), + listenOptions => + { + listenOptions.Protocols = Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols.Http1AndHttp2; + }); + + options.Listen( + IPAddress.Loopback, + int.Parse(args[3]), + listenOptions => + { +#pragma warning disable SYSLIB0057 + var certificate = new X509Certificate2(args[0], args[1]); +#pragma warning restore SYSLIB0057 + + HttpsConnectionAdapterOptions httpsOption = new HttpsConnectionAdapterOptions(); + httpsOption.SslProtocols = SslProtocols.Tls12; + httpsOption.ClientCertificateMode = ClientCertificateMode.AllowCertificate; + httpsOption.ClientCertificateValidation = (inCertificate, inChain, inPolicy) => { return true; }; + httpsOption.CheckCertificateRevocation = false; + httpsOption.ServerCertificate = certificate; + listenOptions.UseHttps(httpsOption); + listenOptions.Protocols = Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols.Http1AndHttp2; + }); + + options.Listen( + IPAddress.Loopback, + int.Parse(args[4]), + listenOptions => + { +#pragma warning disable SYSLIB0057 + var certificate = new X509Certificate2(args[0], args[1]); +#pragma warning restore SYSLIB0057 + + HttpsConnectionAdapterOptions httpsOption = new HttpsConnectionAdapterOptions(); + + // TLS 1.1 is obsolete. Using this value now defaults to TLS 1.2. + httpsOption.SslProtocols = SslProtocols.Tls12; + + httpsOption.ClientCertificateMode = ClientCertificateMode.AllowCertificate; + httpsOption.ClientCertificateValidation = (inCertificate, inChain, inPolicy) => { return true; }; + httpsOption.CheckCertificateRevocation = false; + httpsOption.ServerCertificate = certificate; + listenOptions.UseHttps(httpsOption); + listenOptions.Protocols = Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols.Http1AndHttp2; + }); + + options.Listen( + IPAddress.Loopback, + int.Parse(args[5]), + listenOptions => + { +#pragma warning disable SYSLIB0057 + var certificate = new X509Certificate2(args[0], args[1]); +#pragma warning restore SYSLIB0057 + + HttpsConnectionAdapterOptions httpsOption = new HttpsConnectionAdapterOptions(); + + // TLS is obsolete. Using this value now defaults to TLS 1.2. + httpsOption.SslProtocols = SslProtocols.Tls12; + + httpsOption.ClientCertificateMode = ClientCertificateMode.AllowCertificate; + httpsOption.ClientCertificateValidation = (inCertificate, inChain, inPolicy) => { return true; }; + httpsOption.CheckCertificateRevocation = false; + httpsOption.ServerCertificate = certificate; + listenOptions.UseHttps(httpsOption); + listenOptions.Protocols = Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols.Http1AndHttp2; + }); + + options.Listen( + IPAddress.Loopback, + int.Parse(args[6]), + listenOptions => + { +#pragma warning disable SYSLIB0057 + var certificate = new X509Certificate2(args[0], args[1]); +#pragma warning restore SYSLIB0057 + + HttpsConnectionAdapterOptions httpsOption = new HttpsConnectionAdapterOptions(); + httpsOption.SslProtocols = SslProtocols.Tls13; + httpsOption.ClientCertificateMode = ClientCertificateMode.AllowCertificate; + httpsOption.ClientCertificateValidation = (inCertificate, inChain, inPolicy) => { return true; }; + httpsOption.CheckCertificateRevocation = false; + httpsOption.ServerCertificate = certificate; + listenOptions.UseHttps(httpsOption); + listenOptions.Protocols = Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols.Http1AndHttp2; + }); + }); }) .Build(); } diff --git a/test/tools/WebListener/WebListener.csproj b/test/tools/WebListener/WebListener.csproj index 0f381efed19..3567cd93c68 100644 --- a/test/tools/WebListener/WebListener.csproj +++ b/test/tools/WebListener/WebListener.csproj @@ -7,6 +7,6 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="9.0.8" /> + <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="11.0.0-preview.3.26207.106" /> </ItemGroup> </Project> diff --git a/test/xUnit/csharp/test_AstDefaultVisit.cs b/test/xUnit/csharp/test_AstDefaultVisit.cs index 283c1f73071..41f3ff1ce56 100644 --- a/test/xUnit/csharp/test_AstDefaultVisit.cs +++ b/test/xUnit/csharp/test_AstDefaultVisit.cs @@ -8,17 +8,17 @@ namespace PSTests.Parallel { - internal class MyICustomAstVisitor2 : ICustomAstVisitor2 + internal sealed class MyICustomAstVisitor2 : ICustomAstVisitor2 { public object DefaultVisit(Ast ast) => ast.GetType().Name; } - internal class MyDefaultCustomAstVisitor2 : DefaultCustomAstVisitor2 + internal sealed class MyDefaultCustomAstVisitor2 : DefaultCustomAstVisitor2 { public override object DefaultVisit(Ast ast) => ast.GetType().Name; } - internal class MyAstVisitor2 : AstVisitor2 + internal sealed class MyAstVisitor2 : AstVisitor2 { public List<string> Commands { get; } diff --git a/test/xUnit/csharp/test_CommandLineParser.cs b/test/xUnit/csharp/test_CommandLineParser.cs index 5025584d6ac..01f572d230d 100644 --- a/test/xUnit/csharp/test_CommandLineParser.cs +++ b/test/xUnit/csharp/test_CommandLineParser.cs @@ -48,6 +48,9 @@ public static void TestDefaults() Assert.False(cpp.ShowVersion); Assert.False(cpp.SkipProfiles); Assert.False(cpp.SocketServerMode); +#if !UNIX + Assert.False(cpp.V2SocketServerMode); +#endif Assert.False(cpp.SSHServerMode); if (Platform.IsWindows) { @@ -336,6 +339,25 @@ public static void TestParameter_SocketServerMode(params string[] commandLine) Assert.Null(cpp.ErrorMessage); } +#if !UNIX + [Theory] + [InlineData("-v2socketservermode", "-token", "natoheusatoehusnatoeu", "-utctimestamp", "2023-10-01T12:00:00Z")] + [InlineData("-v2so", "-token", "asentuhasoneuthsaoe", "-utctimestamp", "2025-06-09T12:00:00Z")] + public static void TestParameter_V2SocketServerMode(params string[] commandLine) + { + var cpp = new CommandLineParameterParser(); + + cpp.Parse(commandLine); + + Assert.False(cpp.AbortStartup); + Assert.True(cpp.NoExit); + Assert.False(cpp.ShowShortHelp); + Assert.False(cpp.ShowBanner); + Assert.True(cpp.V2SocketServerMode); + Assert.Null(cpp.ErrorMessage); + } +#endif + [Theory] [InlineData("-servermode")] [InlineData("-s")] diff --git a/test/xUnit/csharp/test_CryptoUtils.cs b/test/xUnit/csharp/test_CryptoUtils.cs index 0f737a94260..6531924d437 100644 --- a/test/xUnit/csharp/test_CryptoUtils.cs +++ b/test/xUnit/csharp/test_CryptoUtils.cs @@ -22,7 +22,7 @@ public static void TestSessionKeyExchange() string publicKey = cryptoClient.GetPublicKeyAsBase64EncodedString(); cryptoServer.ImportPublicKeyFromBase64EncodedString(publicKey); // sent to and imported by server - // generate, export, import session key + // generate, export, import session key cryptoServer.GenerateSessionKey(); // server provides the session key? string sessionKey = cryptoServer.SafeExportSessionKey(); cryptoClient.ImportSessionKeyFromBase64EncodedString(sessionKey); diff --git a/test/xUnit/csharp/test_FileSystemProvider.cs b/test/xUnit/csharp/test_FileSystemProvider.cs index 1bea0f84ce4..06cb43e6088 100644 --- a/test/xUnit/csharp/test_FileSystemProvider.cs +++ b/test/xUnit/csharp/test_FileSystemProvider.cs @@ -43,7 +43,7 @@ public void Dispose() { Dispose(true); GC.SuppressFinalize(this); - } + } protected virtual void Dispose(bool disposing) { diff --git a/test/xUnit/csharp/test_PSConfiguration.cs b/test/xUnit/csharp/test_PSConfiguration.cs index 54e69da68f0..de94107fc6b 100644 --- a/test/xUnit/csharp/test_PSConfiguration.cs +++ b/test/xUnit/csharp/test_PSConfiguration.cs @@ -99,7 +99,7 @@ public void Dispose() { Dispose(true); GC.SuppressFinalize(this); - } + } protected virtual void Dispose(bool disposing) { diff --git a/test/xUnit/csharp/test_RemoteHyperV.cs b/test/xUnit/csharp/test_RemoteHyperV.cs new file mode 100644 index 00000000000..c7cda754161 --- /dev/null +++ b/test/xUnit/csharp/test_RemoteHyperV.cs @@ -0,0 +1,806 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Management.Automation.Language; +using System.Management.Automation.Subsystem; +using System.Management.Automation.Subsystem.Prediction; +using System.Threading; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Reflection; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace PSTests.Sequential +{ + public class RemoteHyperVTests + { + private static ITestOutputHelper _output; + + public RemoteHyperVTests(ITestOutputHelper output) + { + if (!System.Management.Automation.Platform.IsWindows) + { + throw new SkipException("RemoteHyperVTests are only supported on Windows."); + } + + _output = output; + } + + // Helper method to connect with retries + private static void ConnectWithRetry(Socket client, IPAddress address, int port, ITestOutputHelper output, int maxRetries = 10) + { + int retryDelayMs = 500; + int attempt = 0; + bool connected = false; + while (attempt < maxRetries && !connected) + { + try + { + client.Connect(address, port); + connected = true; + } + catch (SocketException) + { + attempt++; + if (attempt < maxRetries) + { + output?.WriteLine($"Connect attempt {attempt} failed, retrying in {retryDelayMs}ms..."); + Thread.Sleep(retryDelayMs); + retryDelayMs *= 2; + } + else + { + output?.WriteLine($"Failed to connect after {maxRetries} attempts. This is most likely an intermittent failure due to environmental issues."); + throw; + } + } + } + } + + private static void SendResponse(string name, Socket client, Queue<(byte[] bytes, int delayMs)> serverResponses) + { + if (serverResponses.Count > 0) + { + _output.WriteLine($"Mock {name} ----------------------------------------------------"); + var respTuple = serverResponses.Dequeue(); + var resp = respTuple.bytes; + + if (respTuple.delayMs > 0) + { + _output.WriteLine($"Mock {name} - delaying response by {respTuple.delayMs} ms"); + Thread.Sleep(respTuple.delayMs); + } + if (resp.Length > 0) { + client.Send(resp, resp.Length, SocketFlags.None); + _output.WriteLine($"Mock {name} - sent response: " + Encoding.ASCII.GetString(resp)); + } + } + } + + private static void StartHandshakeServer( + string name, + int port, + IEnumerable<(string message, Encoding encoding)> expectedClientSends, + IEnumerable<(string message, Encoding encoding)> serverResponses, + bool verifyConnectionClosed, + CancellationToken cancellationToken, + bool sendFirst = false) + { + IEnumerable<(string message, Encoding encoding, int delayMs)> serverResponsesWithDelay = new List<(string message, Encoding encoding, int delayMs)>(); + foreach (var item in serverResponses) + { + ((List<(string message, Encoding encoding, int delayMs)>)serverResponsesWithDelay).Add((item.message, item.encoding, 1)); + } + StartHandshakeServer(name, port, expectedClientSends, serverResponsesWithDelay, verifyConnectionClosed, cancellationToken, sendFirst); + } + + private static void StartHandshakeServer( + string name, + int port, + IEnumerable<(string message, Encoding encoding)> expectedClientSends, + IEnumerable<(string message, Encoding encoding, int delayMs)> serverResponses, + bool verifyConnectionClosed, + CancellationToken cancellationToken, + bool sendFirst = false) + { + var expectedMessages = new Queue<(string message, byte[] bytes, Encoding encoding)>(); + foreach (var item in expectedClientSends) + { + var itemBytes = item.encoding.GetBytes(item.message); + expectedMessages.Enqueue((message: item.message, bytes: itemBytes, encoding: item.encoding)); + } + + var serverResponseBytes = new Queue<(byte[] bytes, int delayMs)>(); + foreach (var item in serverResponses) + { + (byte[] bytes, int delayMs) queueItem = (item.encoding.GetBytes(item.message), item.delayMs); + serverResponseBytes.Enqueue(queueItem); + } + + _output.WriteLine($"Mock {name} - starting listener on port {port} with {expectedMessages.Count} expected messages and {serverResponseBytes.Count} responses."); + StartHandshakeServerImplementation(name, port, expectedMessages, serverResponseBytes, verifyConnectionClosed, cancellationToken, sendFirst); + } + + private static void StartHandshakeServerImplementation( + string name, + int port, + Queue<(string message, byte[] bytes, Encoding encoding)> expectedClientSends, + Queue<(byte[] bytes, int delayMs)> serverResponses, + bool verifyConnectionClosed, + CancellationToken cancellationToken, + bool sendFirst = false) + { + DateTime startTime = DateTime.UtcNow; + var buffer = new byte[1024]; + var listener = new TcpListener(IPAddress.Loopback, port); + listener.Start(); + try + { + using (var client = listener.AcceptSocket()) + { + if (sendFirst) + { + // Send the first message from the serverResponses queue + SendResponse(name, client, serverResponses); + } + + while (expectedClientSends.Count > 0) + { + _output.WriteLine($"Mock {name} - time elapsed: {(DateTime.UtcNow - startTime).TotalMilliseconds} milliseconds"); + client.ReceiveTimeout = 2 * 1000; // 2 seconds timeout for receiving data + cancellationToken.ThrowIfCancellationRequested(); + var expectedMessage = expectedClientSends.Dequeue(); + _output.WriteLine($"Mock {name} - remaining expected messages: {expectedClientSends.Count}"); + var expected = expectedMessage.bytes; + Array.Clear(buffer, 0, buffer.Length); + int received = client.Receive(buffer); + // Optionally validate received data matches expected + string expectedString = expectedMessage.message; + string bufferString = expectedMessage.encoding.GetString(buffer, 0, received); + string alternativeEncodedString = string.Empty; + if (expectedMessage.encoding == Encoding.Unicode) + { + alternativeEncodedString = Encoding.UTF8.GetString(buffer, 0, received); + } + else if (expectedMessage.encoding == Encoding.UTF8) + { + alternativeEncodedString = Encoding.Unicode.GetString(buffer, 0, received); + } + + if (received != expected.Length) + { + string errorMessage = $"Mock {name} - Expected {expected.Length} bytes, but received {received} bytes: `{bufferString}`(alt encoding: `{alternativeEncodedString}`); expected: {expectedString}"; + _output.WriteLine(errorMessage); + throw new Exception(errorMessage); + } + if (!string.Equals(bufferString, expectedString, StringComparison.OrdinalIgnoreCase)) + { + string errorMessage = $"Mock {name} - Expected `{expectedString}`; length {expected.Length}, but received; length {received}; `{bufferString}`(alt encoding: `{alternativeEncodedString}`) instead."; + _output.WriteLine(errorMessage); + throw new Exception(errorMessage); + } + _output.WriteLine($"Mock {name} - received expected message: " + expectedString); + SendResponse(name, client, serverResponses); + } + + if (verifyConnectionClosed) + { + _output.WriteLine($"Mock {name} - verifying client connection is closed."); + // Wait for the client to close the connection synchronously (no timeout) + try + { + while (true) + { + int bytesRead = client.Receive(buffer, SocketFlags.None); + if (bytesRead == 0) + { + break; + } + + // If we receive any data, log and throw (assume UTF8 encoding) + string unexpectedData = Encoding.UTF8.GetString(buffer, 0, bytesRead); + _output.WriteLine($"Mock {name} - received unexpected data after handshake: {unexpectedData}"); + throw new Exception($"Mock {name} - received unexpected data after handshake: {unexpectedData}"); + } + _output.WriteLine($"Mock {name} - client closed the connection."); + } + catch (SocketException ex) + { + _output.WriteLine($"Mock {name} - socket exception while waiting for client close: {ex.Message} {ex.GetType().FullName}"); + } + catch (ObjectDisposedException) + { + _output.WriteLine($"Mock {name} - socket already closed."); + // Socket already closed + } + } + } + + _output.WriteLine($"Mock {name} - on port {port} completed successfully."); + } + catch (Exception ex) + { + _output.WriteLine($"Mock {name} - Exception: {ex.Message} {ex.GetType().FullName}"); + _output.WriteLine(ex.StackTrace); + throw; + } + finally + { + _output.WriteLine($"Mock {name} - remaining expected messages: {expectedClientSends.Count}"); + _output.WriteLine($"Mock {name} - stopping listener on port {port}."); + listener.Stop(); + } + } + + // Helper function to create a random 4-character ASCII response + private static string CreateRandomAsciiResponse() + { + var rand = new Random(); + // Randomly return either "PASS" or "FAIL" + return rand.Next(0, 2) == 0 ? "PASS" : "FAIL"; + } + + // Helper method to create test data + private static (List<(string, Encoding)> expectedClientSends, List<(string, Encoding)> serverResponses) CreateHandshakeTestData(NetworkCredential cred) + { + var expectedClientSends = new List<(string message, Encoding encoding)> + { + (message: cred.Domain, encoding: Encoding.Unicode), + (message: cred.UserName, encoding: Encoding.Unicode), + (message: "NONEMPTYPW", encoding: Encoding.ASCII), + (message: cred.Password, encoding: Encoding.Unicode) + }; + + var serverResponses = new List<(string message, Encoding encoding)> + { + (message: CreateRandomAsciiResponse(), encoding: Encoding.ASCII), // Response to domain + (message: CreateRandomAsciiResponse(), encoding: Encoding.ASCII), // Response to username + (message: CreateRandomAsciiResponse(), encoding: Encoding.ASCII) // Response to non-empty password + }; + + return (expectedClientSends, serverResponses); + } + + private static List<(string message, Encoding encoding)> CreateVersionNegotiationClientSends() + { + return new List<(string message, Encoding encoding)> + { + (message: "VERSION", encoding: Encoding.UTF8), + (message: "VERSION_2", encoding: Encoding.UTF8), + }; + } + + private static List<(string, Encoding)> CreateV2Sends(NetworkCredential cred, string configurationName) + { + var sends = CreateVersionNegotiationClientSends(); + var password = cred.Password; + var emptyPassword = string.IsNullOrEmpty(password); + + sends.AddRange(new List<(string message, Encoding encoding)> + { + (message: cred.Domain, encoding: Encoding.Unicode), + (message: cred.UserName, encoding: Encoding.Unicode) + }); + + if (!emptyPassword) + { + sends.AddRange(new List<(string message, Encoding encoding)> + { + (message: "NONEMPTYPW", encoding: Encoding.UTF8), + (message: cred.Password, encoding: Encoding.Unicode) + }); + } + else + { + sends.Add((message: "EMPTYPW", encoding: Encoding.UTF8)); // Empty password and we don't expect a response + } + + if (!string.IsNullOrEmpty(configurationName)) + { + sends.Add((message: "NONEMPTYCF", encoding: Encoding.UTF8)); + sends.Add((message: configurationName, encoding: Encoding.Unicode)); // Configuration string and we don't expect a response + } + else + { + sends.Add((message: "EMPTYCF", encoding: Encoding.UTF8)); // Configuration string and we don't expect a response + } + + sends.Add((message: "PASS", encoding: Encoding.ASCII)); // Response to TOKEN + + return sends; + } + + private static List<(string, Encoding)> CreateV2Responses(string version = "VERSION_2", bool emptyConfig = false, string token = "FakeToken0+/=", bool emptyPassword = false) + { + var responses = new List<(string message, Encoding encoding)> + { + (message: version, encoding: Encoding.ASCII), // Response to VERSION + (message: "PASS", encoding: Encoding.ASCII), // Response to VERSION_2 + (message: "PASS", encoding: Encoding.ASCII), // Response to domain + (message: "PASS", encoding: Encoding.ASCII), // Response to username + }; + + if (!emptyPassword) + { + responses.Add((message: "PASS", encoding: Encoding.ASCII)); // Response to non-empty password + } + + responses.Add((message: "CONF", encoding: Encoding.ASCII)); // Response to configuration + + if (!emptyConfig) + { + responses.Add((message: "PASS", encoding: Encoding.ASCII)); // Response to non-empty configuration + } + responses.Add((message: "TOKEN " + token, encoding: Encoding.ASCII)); // Response to with a token than uses each class of character in base 64 encoding + + return responses; + } + + // Helper method to create test data + private static (List<(string, Encoding)> expectedClientSends, List<(string, Encoding)> serverResponses) + CreateHandshakeTestDataV2(NetworkCredential cred, string version, string configurationName, string token) + { + bool emptyConfig = string.IsNullOrEmpty(configurationName); + bool emptyPassword = string.IsNullOrEmpty(cred.Password); + return (CreateV2Sends(cred, configurationName), CreateV2Responses(version, emptyConfig, token, emptyPassword)); + } + + // Helper method to create test data + private static (List<(string, Encoding)> expectedClientSends, List<(string, Encoding)> serverResponses) CreateHandshakeTestDataForFallback(NetworkCredential cred) + { + var expectedClientSends = new List<(string message, Encoding encoding)> + { + (message: "VERSION", encoding: Encoding.UTF8), + (message: @"?<PSDirectVMLegacy>", encoding: Encoding.Unicode), + (message: "EMPTYPW", encoding: Encoding.UTF8), // Response to domain + (message: "FAIL", encoding: Encoding.UTF8), // Response to domain + }; + + List<(string message, Encoding encoding)> serverResponses = new List<(string message, Encoding encoding)> + { + (message: "PASS", encoding: Encoding.ASCII), // Response to VERSION but v1 server expects domain so it says "PASS" + (message: "PASS", encoding: Encoding.ASCII), // Response to username + (message: "FAIL", encoding: Encoding.ASCII) // Response to EMPTYPW + }; + + return (expectedClientSends, serverResponses); + } + + // Helper to create a password with at least one non-ASCII Unicode character + public static string CreateRandomUnicodePassword(string prefix) + { + var rand = new Random(); + var asciiPart = new char[6 + prefix.Length]; + // Copy prefix into asciiPart + Array.Copy(prefix.ToCharArray(), 0, asciiPart, 0, prefix.Length); + for (int i = prefix.Length; i < asciiPart.Length; i++) + { + asciiPart[i] = (char)rand.Next(33, 127); // ASCII printable + } + // Add a random Unicode character outside ASCII range (e.g., U+0100 to U+017F) + char unicodeChar = (char)rand.Next(0x0100, 0x017F); + // Insert the unicode character at a random position + int insertPos = rand.Next(0, asciiPart.Length + 1); + var passwordChars = new List<char>(asciiPart); + passwordChars.Insert(insertPos, unicodeChar); + return new string(passwordChars.ToArray()); + } + + public static NetworkCredential CreateTestCredential() + { + return new NetworkCredential(CreateRandomUnicodePassword("username"), CreateRandomUnicodePassword("password"), CreateRandomUnicodePassword("domain")); + } + + [SkippableFact] + public async Task PerformCredentialAndConfigurationHandshake_V1_Pass() + { + // Arrange + int port = 50000 + (int)(DateTime.Now.Ticks % 10000); + var cred = CreateTestCredential(); + string configurationName = CreateRandomUnicodePassword("config"); + + var (expectedClientSends, serverResponses) = CreateHandshakeTestData(cred); + expectedClientSends.Add(("PASS", Encoding.ASCII)); + serverResponses.Add(("PASS", Encoding.ASCII)); + + using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(1)); + var serverTask = Task.Run(() => StartHandshakeServer("Broker", port, expectedClientSends, serverResponses, verifyConnectionClosed: false, cts.Token), cts.Token); + + using (var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + { + ConnectWithRetry(client, IPAddress.Loopback, port, _output); + var exchangeResult = System.Management.Automation.Remoting.RemoteSessionHyperVSocketClient.ExchangeCredentialsAndConfiguration(cred, configurationName, client, true); + var result = exchangeResult.success; + _output.WriteLine($"Exchange result: {result}, Token: {exchangeResult.authenticationToken}"); + System.Threading.Thread.Sleep(100); // Allow time for server to process + Assert.True(result, $"Expected Exchange to pass"); + } + + await serverTask; + } + + [SkippableTheory] + [InlineData("VERSION_2", "configurationname1", "FakeTokenaaaaaaaaaAAAAAAAAAAAAAAAAAAAAAA0FakeTokenaaaaaaaaaAAAAAAAAAAAAAAAAAAAAAA0+/==")] // a fake base64 token about 512 bits long (double the size when this was spec'ed) + [InlineData("VERSION_10", null, "FakeTokenaaaaaaaaaAAAAAAAAAAAAAAAAAAAAAA0+/=")] // a fake base64 token about 256 bits Long (the size when this was spec'ed) + public async Task PerformCredentialAndConfigurationHandshake_V2_Pass(string versionResponse, string configurationName, string token) + { + // Arrange + int port = 50000 + (int)(DateTime.Now.Ticks % 10000); + var cred = CreateTestCredential(); + + var (expectedClientSends, serverResponses) = CreateHandshakeTestDataV2(cred, versionResponse, configurationName, token); + + using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(1)); + var serverTask = Task.Run(() => StartHandshakeServer("Broker", port, expectedClientSends, serverResponses, verifyConnectionClosed: true, cts.Token), cts.Token); + + using (var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + { + client.Connect(IPAddress.Loopback, port); + var exchangeResult = System.Management.Automation.Remoting.RemoteSessionHyperVSocketClient.ExchangeCredentialsAndConfiguration(cred, configurationName, client, false); + var result = exchangeResult.success; + System.Threading.Thread.Sleep(100); // Allow time for server to process + Assert.True(result, $"Expected Exchange to pass for version response '{versionResponse}'"); + Assert.Equal(token, exchangeResult.authenticationToken); + } + + await serverTask; + } + + [SkippableFact] + public async Task PerformCredentialAndConfigurationHandshake_V1_Fallback() + { + // Arrange + int port = 50000 + (int)(DateTime.Now.Ticks % 10000); + var cred = CreateTestCredential(); + string configurationName = CreateRandomUnicodePassword("config"); + + var (expectedClientSends, serverResponses) = CreateHandshakeTestDataForFallback(cred); + + using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(1)); + var serverTask = Task.Run(() => StartHandshakeServer("Broker", port, expectedClientSends, serverResponses, verifyConnectionClosed: false, cts.Token), cts.Token); + + bool isFallback = false; + using (var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + { + _output.WriteLine("Starting handshake with V2 protocol."); + client.Connect(IPAddress.Loopback, port); + var exchangeResult = System.Management.Automation.Remoting.RemoteSessionHyperVSocketClient.ExchangeCredentialsAndConfiguration(cred, configurationName, client, false); + isFallback = !exchangeResult.success; + + System.Threading.Thread.Sleep(100); // Allow time for server to process + _output.WriteLine("Handshake indicated fallback to V1."); + Assert.True(isFallback, "Expected fallback to V1."); + } + _output.WriteLine("Handshake completed successfully with fallback to V1."); + + await serverTask; + } + + [SkippableFact] + public async Task PerformCredentialAndConfigurationHandshake_V2_InvalidResponse() + { + // Arrange + int port = 51000 + (int)(DateTime.Now.Ticks % 10000); + var cred = CreateTestCredential(); + + var (expectedClientSends, serverResponses) = CreateHandshakeTestData(cred); + //expectedClientSends.Add("FAI1"); + serverResponses.Add(("FAI1", Encoding.ASCII)); + + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + + //cts.Token.Register(() => throw new OperationCanceledException("Test timed out.")); + + var serverTask = Task.Run(() => StartHandshakeServer("Broker", port, expectedClientSends, serverResponses, verifyConnectionClosed: false, cts.Token), cts.Token); + + using (var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + { + _output.WriteLine("connecting on port " + port); + ConnectWithRetry(client, IPAddress.Loopback, port, _output); + + var ex = Record.Exception(() => System.Management.Automation.Remoting.RemoteSessionHyperVSocketClient.ExchangeCredentialsAndConfiguration(cred, "config", client, true)); + + try + { + await serverTask; + } + catch (AggregateException exAgg) + { + Assert.Null(exAgg.Flatten().InnerExceptions[1].Message); + } + cts.Token.ThrowIfCancellationRequested(); + + Assert.NotNull(ex); + Assert.NotNull(ex.Message); + Assert.Contains("Hyper-V Broker sent an invalid Credential response", ex.Message); + } + } + + [SkippableFact] + public async Task PerformCredentialAndConfigurationHandshake_V1_Fail() + { + // Arrange + int port = 51000 + (int)(DateTime.Now.Ticks % 10000); + var cred = CreateTestCredential(); + + var (expectedClientSends, serverResponses) = CreateHandshakeTestData(cred); + expectedClientSends.Add(("FAIL", Encoding.ASCII)); + serverResponses.Add(("FAIL", Encoding.ASCII)); + + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15)); + + // This scenario does not close the connection in a timely manner, so we set verifyConnectionClosed to false + var serverTask = Task.Run(() => StartHandshakeServer("Broker", port, expectedClientSends, serverResponses, verifyConnectionClosed: false, cts.Token), cts.Token); + + using (var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + { + client.Connect(IPAddress.Loopback, port); + + var ex = Record.Exception(() => System.Management.Automation.Remoting.RemoteSessionHyperVSocketClient.ExchangeCredentialsAndConfiguration(cred, "config", client, true)); + + try + { + await serverTask; + } + catch (AggregateException exAgg) + { + Assert.Null(exAgg.Flatten().InnerExceptions[1].Message); + } + + cts.Token.ThrowIfCancellationRequested(); + + Assert.NotNull(ex); + Assert.NotNull(ex.Message); + Assert.Contains("The credential is invalid.", ex.Message); + } + } + + [SkippableTheory] + [InlineData("VERSION_2", "FakeTokenaaaaaaaaaAAAAAAAAAAAAAAAAAAAAAA0FakeTokenaaaaaaaaaAAAAAAAAAAAAAAAAAAAAAA0+/==")] // a fake base64 token about 512 bits long (double the size when this was spec'ed) + [InlineData("VERSION_10", "FakeTokenaaaaaaaaaAAAAAAAAAAAAAAAAAAAAAA0+/=")] // a fake base64 token about 256 bits Long (the size when this was spec'ed) + public async Task PerformTransportVersionAndTokenExchange_Pass(string version, string token) + { + // Arrange + int port = 50000 + (int)(DateTime.Now.Ticks % 10000); + var cred = CreateTestCredential(); + + var expectedClientSends = CreateVersionNegotiationClientSends(); + expectedClientSends.Add((message: "TOKEN " + token, encoding: Encoding.ASCII)); + + var serverResponses = new List<(string message, Encoding encoding)>{ + (message: version, encoding: Encoding.ASCII), // Response to VERSION + (message: "PASS", encoding: Encoding.ASCII), // Response to VERSION_2 + (message: "PASS", encoding: Encoding.ASCII) // Response to token + }; + + using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(1)); + var serverTask = Task.Run(() => StartHandshakeServer("Server", port, expectedClientSends, serverResponses, verifyConnectionClosed: true, cts.Token), cts.Token); + + using (var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + { + ConnectWithRetry(client, IPAddress.Loopback, port, _output); + System.Management.Automation.Remoting.RemoteSessionHyperVSocketClient.PerformTransportVersionAndTokenExchange(client, token); + System.Threading.Thread.Sleep(100); // Allow time for server to process + } + + await serverTask; + } + + [SkippableTheory] + [InlineData(1, true)] + [InlineData(2, true)] + [InlineData(0, false)] + [InlineData(null, false)] + [System.Runtime.Versioning.SupportedOSPlatform("windows")] + public void IsRequirePsDirectAuthenticationEnabled(int? regValue, bool expected) + { + const string testKeyPath = @"SOFTWARE\Microsoft\TestRequirePsDirectAuthentication"; + const string valueName = "RequirePsDirectAuthentication"; + if (!System.Management.Automation.Platform.IsWindows) + { + throw new SkipException("RemoteHyperVTests are only supported on Windows."); + } + + // Clean up any previous test key + var regHive = Microsoft.Win32.RegistryHive.CurrentUser; + var baseKey = Microsoft.Win32.RegistryKey.OpenBaseKey(regHive, Microsoft.Win32.RegistryView.Registry64); + baseKey.DeleteSubKeyTree(testKeyPath, false); + + bool? result = null; + + // Create the test key + using (var key = baseKey.CreateSubKey(testKeyPath)) + { + if (regValue.HasValue) + { + key.SetValue(valueName, regValue.Value, Microsoft.Win32.RegistryValueKind.DWord); + } + else + { + // Ensure the value does not exist + key.DeleteValue(valueName, false); + } + + result = System.Management.Automation.Remoting.RemoteSessionHyperVSocketClient.IsRequirePsDirectAuthenticationEnabled(testKeyPath, regHive); + } + + Assert.True(result.HasValue, "IsRequirePsDirectAuthenticationEnabled should return a value."); + Assert.True(expected == result.Value, + $"Expected IsRequirePsDirectAuthenticationEnabled to return {expected} when registry value is {(regValue.HasValue ? regValue.ToString() : "not set")}."); + + return; + } + + [SkippableTheory] + [InlineData("testToken", "testToken")] + [InlineData("testToken\0", "testToken")] + public async Task ValidatePassesWhenTokensMatch(string token, string expectedToken) + { + int port = 50000 + (int)(DateTime.Now.Ticks % 10000); + + var expectedClientSends = new List<(string message, Encoding encoding)>{ + (message: "VERSION", encoding: Encoding.ASCII), // Response to VERSION + (message: "VERSION_2", encoding: Encoding.ASCII), // Response to VERSION_2 + (message: $"TOKEN {token}", encoding: Encoding.ASCII) + }; + + var serverResponses = new List<(string message, Encoding encoding)>{ + (message: "VERSION_2", encoding: Encoding.ASCII), // Response to VERSION_2 + (message: "PASS", encoding: Encoding.ASCII), // Response to VERSION_2 + (message: "PASS", encoding: Encoding.ASCII) // Response to token + }; + + using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(1)); + var serverTask = Task.Run(() => StartHandshakeServer("Client", port, serverResponses, expectedClientSends, verifyConnectionClosed: true, cts.Token, sendFirst: true), cts.Token); + + using (var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + { + ConnectWithRetry(client, IPAddress.Loopback, port, _output); + System.Management.Automation.Remoting.RemoteSessionHyperVSocketServer.ValidateToken(client, expectedToken, DateTimeOffset.UtcNow, 1); + System.Threading.Thread.Sleep(100); // Allow time for server to process + } + + await serverTask; + } + + [SkippableTheory] + [InlineData(5500, "A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.", "SocketException")] // test the socket timeout + [InlineData(3200, "canceled", "System.OperationCanceledException")] // test the cancellation token + [InlineData(10, "", "")] + public async Task ValidateTokenTimeoutFails(int timeoutMs, string expectedMessage, string expectedExceptionType = "SocketException") + { + string token = "testToken"; + string expectedToken = token; + int port = 50000 + (int)(DateTime.Now.Ticks % 10000); + + var expectedClientSends = new List<(string message, Encoding encoding, int delayMs)>{ + (message: "VERSION", encoding: Encoding.ASCII, delayMs: timeoutMs), // Response to VERSION + (message: "VERSION_2", encoding: Encoding.ASCII, delayMs: timeoutMs), // Response to VERSION_2 + (message: $"TOKEN {token}", encoding: Encoding.ASCII, delayMs: 1) + }; + + var serverResponses = new List<(string message, Encoding encoding)>{ + (message: "VERSION_2", encoding: Encoding.ASCII), // Response to VERSION_2 + (message: "PASS", encoding: Encoding.ASCII), // Response to VERSION_2 + (message: "PASS", encoding: Encoding.ASCII) // Response to token + }; + + using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(1)); + var serverTask = Task.Run(() => StartHandshakeServer("Client", port, serverResponses, expectedClientSends, verifyConnectionClosed: true, cts.Token, sendFirst: true), cts.Token); + + using (var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + { + ConnectWithRetry(client, IPAddress.Loopback, port, _output); + if (expectedMessage.Length > 0) + { + var exception = Record.Exception( + () => System.Management.Automation.Remoting.RemoteSessionHyperVSocketServer.ValidateToken(client, expectedToken, DateTimeOffset.UtcNow, 5)); // set the timeout to 5 seconds or 5000 ms + Assert.NotNull(exception); + string exceptionType = exception.GetType().FullName; + _output.WriteLine($"Caught exception of type {exceptionType} with message: {exception.Message}"); + Assert.Contains(expectedExceptionType, exceptionType, StringComparison.OrdinalIgnoreCase); + Assert.Contains(expectedMessage, exception.Message, StringComparison.OrdinalIgnoreCase); + } + else + { + System.Management.Automation.Remoting.RemoteSessionHyperVSocketServer.ValidateToken(client, expectedToken, DateTimeOffset.UtcNow, 5); + } + System.Threading.Thread.Sleep(100); // Allow time for server to process + } + + if (expectedMessage.Length == 0) + { + await serverTask; + } + } + + [SkippableFact] + public async Task ValidateTokenTimeoutDoesAffectSession() + { + string token = "testToken"; + string expectedToken = token; + int port = 50000 + (int)(DateTime.Now.Ticks % 10000); + + var expectedClientSends = new List<(string message, Encoding encoding, int delayMs)>{ + (message: "VERSION", encoding: Encoding.ASCII, delayMs: 1), // Response to VERSION + (message: "VERSION_2", encoding: Encoding.ASCII, delayMs: 1), // Response to VERSION_2 + (message: $"TOKEN {token}", encoding: Encoding.ASCII, delayMs: 1), + (message: string.Empty, encoding: Encoding.ASCII, delayMs: 99), // Send some data after the handshake + (message: string.Empty, encoding: Encoding.ASCII, delayMs: 100), // Send some data after the handshake + (message: string.Empty, encoding: Encoding.ASCII, delayMs: 101), // Send some data after the handshake + (message: string.Empty, encoding: Encoding.ASCII, delayMs: 102), // Send some data after the handshake + (message: string.Empty, encoding: Encoding.ASCII, delayMs: 103) // Send some data after the handshake + }; + + var serverResponses = new List<(string message, Encoding encoding)>{ + (message: "VERSION_2", encoding: Encoding.ASCII), // Response to VERSION_2 + (message: "PASS", encoding: Encoding.ASCII), // Response to VERSION_2 + (message: "PASS", encoding: Encoding.ASCII), // Response to token + (message: "PSRP-Message0", encoding: Encoding.ASCII), // Indicate server is ready to receive data + (message: "PSRP-Message1", encoding: Encoding.ASCII), // Indicate server is ready to receive data + (message: "PSRP-Message2", encoding: Encoding.ASCII), // Indicate server is ready to receive data + (message: "PSRP-Message3", encoding: Encoding.ASCII), // Indicate server is ready to receive data + (message: "PSRP-Message4", encoding: Encoding.ASCII) // + + }; + + using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(2)); + var serverTask = Task.Run(() => StartHandshakeServer("Client", port, serverResponses, expectedClientSends, verifyConnectionClosed: false, cts.Token, sendFirst: true), cts.Token); + + using (var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + { + ConnectWithRetry(client, IPAddress.Loopback, port, _output); + System.Management.Automation.Remoting.RemoteSessionHyperVSocketServer.ValidateToken(client, expectedToken, DateTimeOffset.UtcNow, 5); + for (int i = 0; i < 5; i++) + { + System.Threading.Thread.Sleep(1500); + client.Send(Encoding.ASCII.GetBytes($"PSRP-Message{i}")); // Send some data after the handshake + } + } + + await serverTask; + } + + [SkippableTheory] + [InlineData("abc", "xyz")] + [InlineData("abc", "abcdef")] + [InlineData("abcdef", "abc")] + [InlineData("abc\0def", "abc")] + public async Task ValidateFailsWhenTokensMismatch(string token, string expectedToken) + { + int port = 50000 + (int)(DateTime.Now.Ticks % 10000); + + var expectedClientSends = new List<(string message, Encoding encoding)>{ + (message: "VERSION", encoding: Encoding.ASCII), // Initial request + (message: "VERSION_2", encoding: Encoding.ASCII), // Response to VERSION_2 + (message: $"TOKEN {token}", encoding: Encoding.ASCII) + }; + + var serverResponses = new List<(string message, Encoding encoding)>{ + (message: "VERSION_2", encoding: Encoding.ASCII), // Response to VERSION + (message: "PASS", encoding: Encoding.ASCII), // Response to VERSION_2 + (message: "FAIL", encoding: Encoding.ASCII) // Response to token + }; + + using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(1)); + var serverTask = Task.Run(() => StartHandshakeServer("Client", port, serverResponses, expectedClientSends, verifyConnectionClosed: true, cts.Token, sendFirst: true), cts.Token); + + using (var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + { + ConnectWithRetry(client, IPAddress.Loopback, port, _output); + DateTimeOffset tokenCreationTime = DateTimeOffset.UtcNow; // Token created 10 minutes ago + var exception = Assert.Throws<System.Management.Automation.Remoting.PSDirectException>( + () => System.Management.Automation.Remoting.RemoteSessionHyperVSocketServer.ValidateToken(client, expectedToken, tokenCreationTime, 5)); + System.Threading.Thread.Sleep(100); // Allow time for server to process + Assert.Contains("The credential is invalid.", exception.Message); + } + + await serverTask; + } + } +} diff --git a/test/xUnit/xUnit.tests.csproj b/test/xUnit/xUnit.tests.csproj index 543233d751e..a2a9fe041e9 100644 --- a/test/xUnit/xUnit.tests.csproj +++ b/test/xUnit/xUnit.tests.csproj @@ -24,13 +24,13 @@ <ItemGroup> <PackageReference Include="xunit" Version="2.9.3" /> - <PackageReference Include="Xunit.SkippableFact" Version="1.5.23" /> - <PackageReference Include="xunit.runner.visualstudio" Version="3.1.3"> + <PackageReference Include="Xunit.SkippableFact" Version="1.5.61" /> + <PackageReference Include="xunit.runner.visualstudio" Version="3.1.5"> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <PrivateAssets>all</PrivateAssets> </PackageReference> - <PackageReference Include="XunitXml.TestLogger" Version="6.1.0" /> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" /> + <PackageReference Include="XunitXml.TestLogger" Version="8.0.0" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.4.0" /> </ItemGroup> <ItemGroup> diff --git a/tools/AttackSurfaceAnalyzer/README.md b/tools/AttackSurfaceAnalyzer/README.md new file mode 100644 index 00000000000..f57bb21f8c4 --- /dev/null +++ b/tools/AttackSurfaceAnalyzer/README.md @@ -0,0 +1,249 @@ +# Attack Surface Analyzer Testing + +This directory contains tools for running Attack Surface Analyzer (ASA) tests on PowerShell MSI installations using Docker. + +## Overview + +Attack Surface Analyzer is a Microsoft tool that helps analyze changes to a system's attack surface. These scripts allow you to run ASA tests locally in a clean Windows container to analyze what changes when PowerShell is installed. + +## Files + +- **Run-AttackSurfaceAnalyzer.ps1** - PowerShell script to run ASA tests with official MSIs +- **Summarize-AsaResults.ps1** - PowerShell script to analyze and summarize ASA results +- **docker/Dockerfile** - Multi-stage Dockerfile for building a container image with ASA pre-installed +- **README.md** - This documentation file + +## Docker Architecture + +The Docker implementation uses a multi-stage build to optimize the testing and result extraction process: + +### Multi-Stage Build Stages + +1. **asa-runner**: Main execution environment + - Base: `mcr.microsoft.com/dotnet/sdk:9.0-windowsservercore-ltsc2022` + - Contains Attack Surface Analyzer CLI tools + - Runs the complete test workflow + - Generates reports in both `C:\work` and `C:\reports` directories + +1. **asa-reports**: Minimal results layer + - Base: `mcr.microsoft.com/windows/nanoserver:ltsc2022` + - Contains only the test reports from the runner stage + - Enables clean extraction of results without container internals + +1. **final**: Default stage (inherits from asa-runner) + - Provides backward compatibility + - Used when no specific build target is specified + +### Benefits + +- **Clean Result Extraction**: Reports are isolated in a dedicated layer +- **Efficient Transfer**: Only test results are copied, not the entire container filesystem +- **Fallback Support**: Script includes fallback to volume-based extraction if needed +- **Minimal Footprint**: Final results layer contains only the necessary output files + +## Prerequisites + +- Windows 10/11 or Windows Server +- Docker Desktop with Windows containers enabled +- PowerShell 5.1 or later +- **An official signed PowerShell MSI file** from a released build + +### MSI Requirements + +**Important:** This tool now requires an official, digitally signed PowerShell MSI from Microsoft releases: + +- **Must be signed** by Microsoft Corporation +- **Must be from an official release** (downloaded from [PowerShell Releases](https://github.com/PowerShell/PowerShell/releases)) +- **Local builds are not supported** - unsigned or development MSIs will be rejected +- The script automatically verifies the digital signature before proceeding + +**Where to get official MSIs:** + +- Download from: https://github.com/PowerShell/PowerShell/releases +- Look for files like: `PowerShell-7.x.x-win-x64.msi` + +## Quick Start + +### Option 1: Using the PowerShell Script (Recommended) + +The script requires an official signed PowerShell MSI file: + +```powershell +# Run ASA test with official MSI (MsiPath is required) +.\tools\AttackSurfaceAnalyzer\Run-AttackSurfaceAnalyzer.ps1 -MsiPath "C:\path\to\PowerShell-7.4.0-win-x64.msi" + +# Specify custom output directory for results +.\tools\AttackSurfaceAnalyzer\Run-AttackSurfaceAnalyzer.ps1 -MsiPath ".\PowerShell-7.4.0-win-x64.msi" -OutputPath "C:\asa-results" + +# Keep the temporary work directory for debugging +.\tools\AttackSurfaceAnalyzer\Run-AttackSurfaceAnalyzer.ps1 -MsiPath ".\PowerShell-7.4.0-win-x64.msi" -KeepWorkDirectory +``` + +The script will: + +1. **Verify MSI signature** - Ensures the MSI is officially signed by Microsoft Corporation +1. Create a temporary work directory +1. Build a custom Docker container from the static Dockerfile +1. Start the Windows container with Attack Surface Analyzer +1. Take a baseline snapshot +1. Install the PowerShell MSI +1. Take a post-installation snapshot +1. Export comparison results +1. Copy results back to your specified output directory + +**Security Note:** The script will reject any MSI that is not digitally signed by Microsoft Corporation to ensure analysis is performed only on official releases. + +### Option 2: Using the Dockerfile + +If you prefer to build and use the container image directly: + +```powershell +# Build the Docker image (Dockerfile is in docker subfolder with clean context) +docker build -f tools\AttackSurfaceAnalyzer\docker\Dockerfile -t powershell-asa-test tools\AttackSurfaceAnalyzer\docker\ + +# Run the container with your MSI (script is built into the container) +docker run --rm --isolation process ` + -v "C:\path\to\msi\directory:C:\work" ` + powershell-asa-test +``` + +## Output Files + +The test will generate output files in the `./asa-results/` directory (or your specified `-OutputPath`): + +- **`asa.sqlite`** - SQLite database with full analysis data (primary result file) +- **`install.log`** - MSI installation log file +- **`*_summary.json.txt`** - Summary of detected changes (if generated) +- **`*_results.json.txt`** - Detailed results in JSON format (if generated) +- **`*.sarif`** - SARIF format results (if generated, can be viewed in VS Code) + +## Analyzing Results + +### Using the Summary Script (Recommended) + +Use the included summary script to get a comprehensive analysis: + +```powershell +# Basic summary of ASA results +.\tools\AttackSurfaceAnalyzer\Summarize-AsaResults.ps1 + +# Detailed analysis with rule breakdowns +.\tools\AttackSurfaceAnalyzer\Summarize-AsaResults.ps1 -ShowDetails + +# Analyze results from a specific location +.\tools\AttackSurfaceAnalyzer\Summarize-AsaResults.ps1 -Path "C:\custom\path\asa-results.json" -ShowDetails +``` + +The summary script provides: + +- **Overall statistics** - Total findings, analysis levels, category breakdowns +- **Rule analysis** - Which security rules were triggered and how often +- **File analysis** - Detailed breakdown of file-related security issues by rule type +- **Category cross-reference** - Shows which rules affect which categories + +### Using VS Code + +The SARIF files can be opened directly in VS Code with the SARIF Viewer extension to see a formatted view of the findings. + +### Using PowerShell + +```powershell +# Read the JSON results directly +$results = Get-Content "asa-results\asa-results.json" | ConvertFrom-Json +$results.Results.FILE_CREATED.Count # Number of files created + +# Query the SQLite database (requires SQLite tools) +# Example: List all file changes +# sqlite3 asa.sqlite "SELECT * FROM file_system WHERE change_type != 'NONE'" +``` + +## Troubleshooting + +### Docker Not Available + +The script automatically handles Docker Desktop installation and startup: + +**If Docker Desktop is installed but not running:** + +- The script will automatically start Docker Desktop for you +- It waits up to 60 seconds for Docker to become available +- You'll be prompted for confirmation (supports `-Confirm` and `-WhatIf`) + +**If Docker Desktop is not installed:** + +- The script will prompt you to install it automatically using winget +- After installation completes, start Docker Desktop and run the script again + +**Manual Installation:** + +1. Install Docker Desktop from https://www.docker.com/products/docker-desktop +1. Ensure Docker is running +1. Switch to Windows containers (right-click Docker tray icon → "Switch to Windows containers") + +### Container Fails to Start + +- Ensure you have enough disk space (containers can be large) +- Check that Windows containers are enabled in Docker settings +- Try pulling the base image manually: `docker pull mcr.microsoft.com/dotnet/sdk:9.0-windowsservercore-ltsc2022` + +### MSI Signature Verification Fails + +If you get signature verification errors: + +- **Ensure you're using an official MSI** from [PowerShell Releases](https://github.com/PowerShell/PowerShell/releases) +- **Do not use local builds** - only signed release MSIs are supported +- **Check certificate validity** - very old MSIs may have expired certificates +- **Verify file integrity** - redownload the MSI if it may be corrupted + +### No Results Generated + +- Check the install.log file for MSI installation errors +- Run with `-KeepWorkDirectory` to inspect the temporary work directory +- Verify the MSI file is valid and not corrupted + +## Advanced Usage + +### Parameters + +The `Run-AttackSurfaceAnalyzer.ps1` script supports these parameters: + +- **`-MsiPath`** (Required) - Path to the official signed PowerShell MSI file +- **`-OutputPath`** (Optional) - Directory for results (defaults to `./asa-results`) +- **`-ContainerImage`** (Optional) - Custom container base image +- **`-KeepWorkDirectory`** (Optional) - Keep temp directory for debugging + +Example with custom container image: + +```powershell +.\tools\AttackSurfaceAnalyzer\Run-AttackSurfaceAnalyzer.ps1 ` + -MsiPath ".\PowerShell-7.4.0-win-x64.msi" ` + -ContainerImage "mcr.microsoft.com/dotnet/sdk:8.0-windowsservercore-ltsc2022" +``` + +### Debugging + +To debug issues, keep the work directory and examine the files: + +```powershell +.\tools\AttackSurfaceAnalyzer\Run-AttackSurfaceAnalyzer.ps1 -KeepWorkDirectory + +# The script will print the work directory path +# You can then examine: +# - run-asa.ps1 - The script that runs in the container +# - install.log - MSI installation log +# - Any other generated files +``` + +## Integration with CI/CD + +These tools were extracted from the GitHub Actions workflow to allow local testing. If you need to integrate ASA testing back into a CI/CD pipeline, you can: + +1. Use the PowerShell script directly in your pipeline +1. Build and push the Docker image to a registry +1. Use the Dockerfile as a base for custom testing scenarios + +## More Information + +- [Attack Surface Analyzer on GitHub](https://github.com/microsoft/AttackSurfaceAnalyzer) +- [Docker for Windows Documentation](https://docs.docker.com/desktop/windows/) +- [SARIF Documentation](https://sarifweb.azurewebsites.net/) diff --git a/tools/AttackSurfaceAnalyzer/Run-AttackSurfaceAnalyzer.ps1 b/tools/AttackSurfaceAnalyzer/Run-AttackSurfaceAnalyzer.ps1 new file mode 100644 index 00000000000..2f7e502bff6 --- /dev/null +++ b/tools/AttackSurfaceAnalyzer/Run-AttackSurfaceAnalyzer.ps1 @@ -0,0 +1,590 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +<# +.SYNOPSIS + Run Attack Surface Analyzer test locally using Docker to analyze PowerShell MSI installation. + +.DESCRIPTION + This script runs Attack Surface Analyzer in a clean Windows container to analyze + the attack surface changes when installing PowerShell MSI. It takes a baseline + snapshot, installs the MSI, takes a post-installation snapshot, and exports the + comparison results. + +.PARAMETER MsiPath + Path to the official signed PowerShell MSI file to test. This must be a released, + signed MSI from the official PowerShell releases. + +.PARAMETER OutputPath + Directory where results will be saved. Defaults to './asa-results' subdirectory. + +.PARAMETER ContainerImage + Docker container image to use. Defaults to mcr.microsoft.com/dotnet/sdk:9.0-windowsservercore-ltsc2022 + +.PARAMETER KeepWorkDirectory + If specified, keeps the temporary work directory after the test completes. + +.EXAMPLE + .\Run-AttackSurfaceAnalyzer.ps1 -MsiPath "C:\path\to\PowerShell-7.4.0-win-x64.msi" + +.EXAMPLE + .\Run-AttackSurfaceAnalyzer.ps1 -MsiPath ".\PowerShell-7.4.0-win-x64.msi" -OutputPath "C:\asa-results" + +.NOTES + Requires Docker Desktop with Windows containers enabled. + Requires an official signed PowerShell MSI file from a released build. + + Docker Desktop Handling: + - If Docker Desktop is installed but not running, the script will start it automatically + - If Docker Desktop is not installed, the script will prompt to install it using winget + - Waits up to 60 seconds for Docker to become available after starting + + MSI Requirements: + - The MSI must be digitally signed by Microsoft Corporation + - The MSI must be from an official PowerShell release + - Local builds or unsigned MSIs are not supported + + Supports -WhatIf and -Confirm for Docker installation and startup. +#> + +[CmdletBinding(SupportsShouldProcess)] +param( + [Parameter(Mandatory)] + [string]$MsiPath, + + [Parameter()] + [string]$OutputPath = (Join-Path $PWD "asa-results"), + + [Parameter()] + [string]$ContainerImage = "mcr.microsoft.com/dotnet/sdk:9.0-windowsservercore-ltsc2022", + + [Parameter()] + [switch]$KeepWorkDirectory +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +function Write-Log { + param([string]$Message, [string]$Level = "INFO") + $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" + $color = switch ($Level) { + "ERROR" { "Red" } + "WARNING" { "Yellow" } + "SUCCESS" { "Green" } + default { "White" } + } + Write-Host "[$timestamp] [$Level] $Message" -ForegroundColor $color +} + +function Test-MsiSignature { + param( + [Parameter(Mandatory)] + [string]$MsiPath + ) + + Write-Log "Verifying MSI signature..." -Level INFO + + try { + # Get the digital signature information + $signature = Get-AuthenticodeSignature -FilePath $MsiPath + + if ($signature.Status -ne 'Valid') { + Write-Log "MSI signature is not valid. Status: $($signature.Status)" -Level ERROR + return $false + } + + # Check if signed by Microsoft Corporation + $signerCertificate = $signature.SignerCertificate + if (-not $signerCertificate) { + Write-Log "No signer certificate found" -Level ERROR + return $false + } + + $subject = $signerCertificate.Subject + Write-Log "Certificate subject: $subject" -Level INFO + + # Check for Microsoft Corporation in the subject + if ($subject -notmatch "Microsoft Corporation" -and $subject -notmatch "CN=Microsoft Corporation") { + Write-Log "MSI is not signed by Microsoft Corporation" -Level ERROR + Write-Log "Expected: Microsoft Corporation" -Level ERROR + Write-Log "Found: $subject" -Level ERROR + return $false + } + + # Check certificate validity + $validFrom = $signerCertificate.NotBefore + $validTo = $signerCertificate.NotAfter + $now = Get-Date + + if ($now -lt $validFrom -or $now -gt $validTo) { + Write-Log "Certificate is not valid for current date" -Level ERROR + Write-Log "Valid from: $validFrom to: $validTo" -Level ERROR + return $false + } + + Write-Log "MSI signature verification passed" -Level SUCCESS + Write-Log "Signed by: $($signerCertificate.Subject)" -Level SUCCESS + Write-Log "Valid from: $validFrom to: $validTo" -Level SUCCESS + + return $true + } + catch { + Write-Log "Error verifying MSI signature: $_" -Level ERROR + return $false + } +} + +function Test-DockerAvailable { + try { + $null = docker version 2>&1 + return $true + } + catch { + return $false + } +} + +function Test-DockerDesktopInstalled { + # Check if Docker Desktop executable exists + $dockerDesktopPaths = @( + "${env:ProgramFiles}\Docker\Docker\Docker Desktop.exe", + "${env:ProgramFiles(x86)}\Docker\Docker\Docker Desktop.exe", + "${env:LOCALAPPDATA}\Programs\Docker\Docker Desktop.exe" + ) + + foreach ($path in $dockerDesktopPaths) { + if (Test-Path $path) { + return $path + } + } + return $null +} + +function Test-DockerDesktopRunning { + $process = Get-Process -Name "Docker Desktop" -ErrorAction SilentlyContinue + return $null -ne $process +} + +function Start-DockerDesktopApp { + [CmdletBinding(SupportsShouldProcess)] + param() + + $dockerDesktopPath = Test-DockerDesktopInstalled + + if (-not $dockerDesktopPath) { + Write-Log "Docker Desktop executable not found." -Level ERROR + return $false + } + + if (Test-DockerDesktopRunning) { + Write-Log "Docker Desktop is already running." -Level SUCCESS + return $true + } + + if ($PSCmdlet.ShouldProcess("Docker Desktop", "Start application")) { + Write-Log "Starting Docker Desktop..." -Level SUCCESS + Write-Log "This may take a minute for Docker to fully start..." + + try { + Start-Process -FilePath $dockerDesktopPath -WindowStyle Hidden + + # Wait for Docker to become available (up to 60 seconds) + $maxWaitSeconds = 60 + $waitedSeconds = 0 + + while ($waitedSeconds -lt $maxWaitSeconds) { + Start-Sleep -Seconds 5 + $waitedSeconds += 5 + Write-Log "Waiting for Docker to start... ($waitedSeconds/$maxWaitSeconds seconds)" + + if (Test-DockerAvailable) { + Write-Log "Docker Desktop started successfully!" -Level SUCCESS + return $true + } + } + + Write-Log "Docker Desktop was started but is not responding yet. Please wait a moment and try again." -Level WARNING + return $false + } + catch { + Write-Log "Error starting Docker Desktop: $_" -Level ERROR + return $false + } + } + else { + Write-Log "Starting Docker Desktop cancelled by user." -Level WARNING + return $false + } +} + +function Test-WingetAvailable { + try { + $null = Get-Command winget -ErrorAction Stop + return $true + } + catch { + return $false + } +} + +function Install-DockerDesktop { + [CmdletBinding(SupportsShouldProcess, ConfirmImpact="High")] + param() + + if (-not (Test-WingetAvailable)) { + Write-Log "winget is not available. Please install winget (App Installer from Microsoft Store) or install Docker Desktop manually from https://www.docker.com/products/docker-desktop" -Level ERROR + return $false + } + + if ($PSCmdlet.ShouldProcess("Docker Desktop", "Install using winget")) { + Write-Log "Installing Docker Desktop using winget..." -Level SUCCESS + Write-Log "This may take several minutes..." + + try { + winget install docker.dockerdesktop --accept-package-agreements --accept-source-agreements + + if ($LASTEXITCODE -eq 0) { + Write-Log "Docker Desktop installed successfully!" -Level SUCCESS + Write-Log "Please restart Docker Desktop and ensure Windows containers are enabled, then run this script again." -Level SUCCESS + return $true + } + else { + Write-Log "Docker Desktop installation failed with exit code: $LASTEXITCODE" -Level ERROR + return $false + } + } + catch { + Write-Log "Error installing Docker Desktop: $_" -Level ERROR + return $false + } + } + else { + Write-Log "Docker Desktop installation cancelled by user." -Level WARNING + return $false + } +} + +# Verify Docker is available +Write-Log "Checking Docker availability..." +if (-not (Test-DockerAvailable)) { + Write-Log "Docker is not responding." -Level WARNING + + # Check if Docker Desktop is installed but not running + if (Test-DockerDesktopInstalled) { + Write-Log "Docker Desktop is installed but not running." -Level WARNING + + if (Start-DockerDesktopApp) { + Write-Log "Docker Desktop is now running and ready." -Level SUCCESS + } + else { + Write-Log "Failed to start Docker Desktop or it's taking longer than expected." -Level ERROR + Write-Log "Please start Docker Desktop manually and ensure Windows containers are enabled, then run this script again." -Level ERROR + exit 1 + } + } + else { + # Docker Desktop is not installed + Write-Log "Docker Desktop is not installed." -Level WARNING + Write-Log "Docker Desktop is required to run Attack Surface Analyzer tests in containers." -Level WARNING + + if (Install-DockerDesktop) { + Write-Log "Docker Desktop has been installed. Please restart Docker Desktop and run this script again." -Level SUCCESS + exit 0 + } + else { + Write-Log "Please install Docker Desktop manually from https://www.docker.com/products/docker-desktop and ensure it's running with Windows containers enabled." -Level ERROR + exit 1 + } + } +} + +# Verify MSI exists and is properly signed +if (-not (Test-Path $MsiPath)) { + Write-Log "MSI file not found: $MsiPath" -Level ERROR + exit 1 +} + +$MsiPath = Resolve-Path $MsiPath +Write-Log "Using MSI: $MsiPath" + +# Verify MSI signature +if (-not (Test-MsiSignature -MsiPath $MsiPath)) { + Write-Log "MSI signature verification failed. Only official signed PowerShell MSIs are supported." -Level ERROR + Write-Log "Please download an official PowerShell MSI from: https://github.com/PowerShell/PowerShell/releases" -Level ERROR + exit 1 +} + +# Create output directory +$OutputPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($OutputPath) +if (-not (Test-Path $OutputPath)) { + New-Item -ItemType Directory -Path $OutputPath -Force | Out-Null + Write-Log "Created output directory: $OutputPath" +} + +# Create container work directory +$containerWorkDir = Join-Path $env:TEMP "asa-container-work-$(Get-Date -Format 'yyyyMMdd-HHmmss')" +New-Item -ItemType Directory -Force -Path $containerWorkDir | Out-Null +Write-Log "Created container work directory: $containerWorkDir" + +try { + # Use the static Dockerfile from the docker subfolder + $dockerContextPath = Join-Path $PSScriptRoot "docker" + + # Copy MSI to Docker build context + $msiFileName = Split-Path $MsiPath -Leaf + $destMsiPath = Join-Path $dockerContextPath $msiFileName + Write-Log "Copying MSI to Docker build context..." + Copy-Item $MsiPath -Destination $destMsiPath + $staticDockerfilePath = Join-Path $dockerContextPath "Dockerfile" + Write-Log "Using static Dockerfile: $staticDockerfilePath" + + if (-not (Test-Path $staticDockerfilePath)) { + Write-Log "Static Dockerfile not found at: $staticDockerfilePath" -Level ERROR + exit 1 + } + + Write-Log "Docker build context: $dockerContextPath" + + # Build custom container image from static Dockerfile + Write-Log "=========================================" -Level SUCCESS + Write-Log "Building custom Attack Surface Analyzer container..." -Level SUCCESS + Write-Log "=========================================" -Level SUCCESS + + Write-Log "=========================================" -Level SUCCESS + Write-Log "Building ASA test container..." -Level SUCCESS + Write-Log "=========================================" -Level SUCCESS + Write-Log "This may take several minutes..." + + # Build the asa-reports stage specifically + $reportsImageName = "powershell-asa-reports:latest" + docker build --target asa-reports -t $reportsImageName -f $staticDockerfilePath $dockerContextPath + + if ($LASTEXITCODE -ne 0) { + Write-Log "Docker build failed with exit code: $LASTEXITCODE" -Level ERROR + exit 1 + } + + Write-Log "Build completed successfully" -Level SUCCESS + + # Extract reports from the built image + Write-Log "=========================================" -Level SUCCESS + Write-Log "Extracting reports to: $OutputPath" -Level SUCCESS + Write-Log "=========================================" -Level SUCCESS + + $tempContainerName = "asa-reports-extract-$(Get-Date -Format 'yyyyMMdd-HHmmss')" + + try { + # Create a container from the reports image (but don't run it) + docker create --name $tempContainerName $reportsImageName + + if ($LASTEXITCODE -ne 0) { + Write-Log "Failed to create temporary container for extraction" -Level ERROR + exit 1 + } + + # Try to extract known report file patterns individually + Write-Log "Extracting report files..." -Level INFO + + # Extract standardized report files directly (no file listing needed) + # Extract files with standardized names (no wildcards needed) + Write-Log "Extracting standardized report files..." -Level INFO + $reportFilePatterns = @( + "asa.sqlite", + "asa-results.json", + "install.log" + ) + + $extractedAny = $false + + foreach ($filename in $reportFilePatterns) { + try { + Write-Log "Trying to extract file: $filename" -Level INFO + docker cp "${tempContainerName}:/$filename" $OutputPath 2>$null + + if ($LASTEXITCODE -eq 0) { + Write-Log "Successfully extracted: $filename" -Level SUCCESS + $extractedAny = $true + } else { + Write-Log "File not found: $filename" -Level INFO + } + } + catch { + Write-Log "Error extracting file $filename : $_" -Level WARNING + } + } + + # Alternative approach: extract the entire reports directory if individual files don't work + if (-not $extractedAny) { + Write-Log "Trying to extract entire directory..." -Level INFO + docker cp "${tempContainerName}:/" "$OutputPath/reports" 2>$null + + if ($LASTEXITCODE -eq 0) { + Write-Log "Successfully extracted reports directory" -Level SUCCESS + $extractedAny = $true + } + } + + if ($extractedAny) { + Write-Log "Report extraction completed successfully" -Level SUCCESS + } else { + Write-Log "No reports could be extracted - this may be normal if no issues were found" -Level WARNING + } + } + finally { + # Clean up the temporary container + docker rm $tempContainerName -f 2>$null + } + + # Check what files were extracted + Write-Host "" + Write-Log "=========================================" -Level SUCCESS + Write-Log "Checking extracted results..." -Level SUCCESS + Write-Log "=========================================" -Level SUCCESS + + $resultFiles = Get-ChildItem -Path $OutputPath -ErrorAction SilentlyContinue + $copiedCount = $resultFiles.Count + + if ($copiedCount -eq 0) { + Write-Log "Warning: No result files found in extracted output" -Level WARNING + } + else { + Write-Log "Successfully extracted $copiedCount file(s):" -Level SUCCESS + $resultFiles | ForEach-Object { + if ($_.PSIsContainer) { + Write-Log " - $($_.Name) (directory)" -Level SUCCESS + } else { + Write-Log " - $($_.Name) ($([math]::Round($_.Length/1KB, 2)) KB)" -Level SUCCESS + } + } + } + + Write-Host "" + Write-Log "=========================================" -Level SUCCESS + Write-Log "Attack Surface Analyzer test completed!" -Level SUCCESS + Write-Log "=========================================" -Level SUCCESS + Write-Log "Results saved to: $OutputPath" -Level SUCCESS + + # Check for ASA GUI availability and launch interactive analysis + $dbPath = Join-Path $OutputPath "asa.sqlite" + $jsonPath = Join-Path $OutputPath "asa-results.json" + + if (Test-Path $dbPath) { + # Check if ASA CLI is available + $asaAvailable = $false + try { + $asaVersion = asa --version 2>$null + if ($LASTEXITCODE -eq 0) { + $asaAvailable = $true + Write-Log "Attack Surface Analyzer CLI detected: $($asaVersion.Trim())" -Level INFO + } + } + catch { + # ASA not available via PATH + } + + # Try dotnet tool global path if ASA not found in PATH + if (-not $asaAvailable) { + $globalToolsPath = "$env:USERPROFILE\.dotnet\tools\asa.exe" + if (Test-Path $globalToolsPath) { + try { + $asaVersion = & $globalToolsPath --version 2>$null + if ($LASTEXITCODE -eq 0) { + $asaAvailable = $true + Write-Log "Attack Surface Analyzer found in global tools: $($asaVersion.Trim())" -Level INFO + # Use full path for subsequent commands + $asaCommand = $globalToolsPath + } + } + catch { + # Global tools ASA not working + } + } + } else { + $asaCommand = "asa" + } + + if ($asaAvailable) { + Write-Log "Launching Attack Surface Analyzer GUI for interactive analysis..." -Level SUCCESS + try { + # Launch ASA GUI with the database file + $asaProcess = Start-Process -FilePath $asaCommand -ArgumentList "gui", "--databasefilename", "`"$dbPath`"" -PassThru -NoNewWindow:$false + + if ($asaProcess) { + Write-Log "ASA GUI launched successfully (PID: $($asaProcess.Id))" -Level SUCCESS + Write-Log "Interactive analysis interface is now available" -Level INFO + } else { + Write-Log "Failed to launch ASA GUI" -Level WARNING + } + } + catch { + Write-Log "Error launching ASA GUI: $_" -Level WARNING + Write-Log "You can manually launch the GUI with: asa gui --databasefilename `"$dbPath`"" -Level INFO + } + } else { + Write-Log "Attack Surface Analyzer CLI not found" -Level INFO + Write-Log "Install ASA globally to enable GUI analysis: dotnet tool install -g Microsoft.CST.AttackSurfaceAnalyzer.CLI" -Level INFO + Write-Log "Then launch GUI manually with: asa gui --databasefilename `"$dbPath`"" -Level INFO + } + } else { + Write-Log "Database file not found - cannot launch ASA GUI" -Level WARNING + } + + # Also check for VS Code integration for JSON analysis + if (Test-Path $jsonPath) { + # Detect if running in VS Code + $isVSCode = $false + + if ($env:VSCODE_PID -or $env:TERM_PROGRAM -eq "vscode" -or $env:VSCODE_INJECTION -eq "1") { + $isVSCode = $true + } + + # Check if 'code' command is available + if (-not $isVSCode) { + try { + $null = & code --version 2>$null + if ($LASTEXITCODE -eq 0) { + $isVSCode = $true + } + } + catch { + # 'code' command not available + } + } + + if ($isVSCode) { + Write-Log "VS Code detected - opening JSON results for analysis..." -Level INFO + try { + & code $jsonPath + if ($LASTEXITCODE -eq 0) { + Write-Log "JSON results file opened in VS Code: $jsonPath" -Level SUCCESS + } else { + Write-Log "Failed to open JSON file in VS Code" -Level WARNING + } + } + catch { + Write-Log "Error opening JSON file in VS Code: $_" -Level WARNING + } + } else { + Write-Log "JSON analysis results available at: $jsonPath" -Level INFO + Write-Log "Open this file in VS Code or any JSON viewer for detailed analysis" -Level INFO + } + } +} +finally { + # Cleanup + if (-not $KeepWorkDirectory) { + Write-Log "Cleaning up temporary work directory..." + Remove-Item -Path $containerWorkDir -Recurse -Force -ErrorAction SilentlyContinue + Write-Log "Cleanup completed" + } + else { + Write-Log "Work directory preserved at: $containerWorkDir" -Level SUCCESS + } + + # Always cleanup MSI file from Docker build context + if ($destMsiPath -and (Test-Path $destMsiPath)) { + Write-Log "Cleaning up MSI file from Docker context..." + Remove-Item -Path $destMsiPath -Force -ErrorAction SilentlyContinue + } +} diff --git a/tools/AttackSurfaceAnalyzer/Summarize-AsaResults.ps1 b/tools/AttackSurfaceAnalyzer/Summarize-AsaResults.ps1 new file mode 100644 index 00000000000..00f27014037 --- /dev/null +++ b/tools/AttackSurfaceAnalyzer/Summarize-AsaResults.ps1 @@ -0,0 +1,636 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +#Requires -Version 5.1 +<# +.SYNOPSIS + Summarizes Attack Surface Analyzer (ASA) results from a JSON file. + +.DESCRIPTION + This script analyzes ASA JSON results and provides a comprehensive summary of security findings, + including counts by category, analysis levels, and detailed breakdowns of security issues. + +.PARAMETER Path + Path to the ASA results JSON file. Defaults to 'asa-results\asa-results.json' in the current directory. + +.PARAMETER ShowDetails + Shows detailed information about each finding category. + +.PARAMETER IncludeInformationalEvent + Includes informational events in the analysis. By default, only WARNING and ERROR events are processed. + +.PARAMETER IncludeDebugEvent + Includes debug events in the analysis. By default, only WARNING and ERROR events are processed. + +.EXAMPLE + .\Summarize-AsaResults.ps1 + + Summarizes the ASA results with basic statistics, showing only WARNING and ERROR events. + +.EXAMPLE + .\Summarize-AsaResults.ps1 -ShowDetails + + Shows detailed breakdown of findings by category, filtering out informational and debug events. + +.EXAMPLE + .\Summarize-AsaResults.ps1 -IncludeInformationalEvent + + Includes informational events along with WARNING and ERROR events in the analysis..NOTES + Author: GitHub Copilot + Version: 1.0 + Created for PowerShell ASA Analysis +#> + +[CmdletBinding()] +param( + [Parameter()] + [string]$Path = "asa-results\asa-results.json", + + [Parameter()] + [switch]$ShowDetails, + + [Parameter()] + [switch]$IncludeInformationalEvent, + + [Parameter()] + [switch]$IncludeDebugEvent +) + +function Get-AsaSummary { + param( + [Parameter(Mandatory)] + $AsaData, + + [Parameter()] + [switch]$IncludeInformationalEvent, + + [Parameter()] + [switch]$IncludeDebugEvent + ) + + # Extract metadata + $metadata = $AsaData["Metadata"] + $results = $AsaData["Results"] + + # Initialize counters + $summary = @{ + Metadata = @{ + Version = $metadata["compare-version"] + OS = $metadata["compare-os"] + OSVersion = $metadata["compare-osversion"] + BaseRunId = "" + CompareRunId = "" + } + Categories = @{} + TotalFindings = 0 + AnalysisLevels = @{ + WARNING = 0 + ERROR = 0 + INFORMATION = 0 + DEBUG = 0 + } + RuleTypes = @{} + FileIssuesByRule = @{} + FileExtensionSummary = @{} + TimeSpan = $null + } + + # Process each category + foreach ($categoryName in $results.Keys) { + $categoryData = $results[$categoryName] + + $summary.Categories[$categoryName] = @{ + Count = 0 + Items = @() + } + + # Process items in category with filtering + foreach ($item in $categoryData) { + # Filter events based on analysis level + $analysisLevel = $item["Analysis"] + if ($analysisLevel) { + # Skip informational events unless explicitly included + if ($analysisLevel -eq "INFORMATION" -and -not $IncludeInformationalEvent) { + continue + } + # Skip debug events unless explicitly included + if ($analysisLevel -eq "DEBUG" -and -not $IncludeDebugEvent) { + continue + } + + $summary.AnalysisLevels[$analysisLevel]++ + } # If we reach here, the item passed the filter + $summary.Categories[$categoryName].Count++ + $summary.TotalFindings++ + + # Extract run IDs and calculate timespan + if ($item["BaseRunId"]) { + $summary.Metadata.BaseRunId = $item["BaseRunId"] + } + if ($item["CompareRunId"]) { + $summary.Metadata.CompareRunId = $item["CompareRunId"] + } + + # Process rules + foreach ($rule in $item["Rules"]) { + $ruleName = $rule["Name"] + if (-not $summary.RuleTypes.ContainsKey($ruleName)) { + $summary.RuleTypes[$ruleName] = @{ + Count = 0 + Description = $rule["Description"] + Flag = $rule["Flag"] + Platforms = $rule["Platforms"] + Categories = @{} + } + } + $summary.RuleTypes[$ruleName].Count++ + + # Track which categories this rule appears in + if (-not $summary.RuleTypes[$ruleName].Categories.ContainsKey($categoryName)) { + $summary.RuleTypes[$ruleName].Categories[$categoryName] = 0 + } + $summary.RuleTypes[$ruleName].Categories[$categoryName]++ + + # For file-related categories, track file extension if available + if ($categoryName -like "*FILE*" -and $item["Identity"]) { + $fileExtension = [System.IO.Path]::GetExtension($item["Identity"]).ToLower() + if (-not $fileExtension) { $fileExtension = "(no extension)" } + + # Track by rule and extension + if (-not $summary.FileIssuesByRule.ContainsKey($ruleName)) { + $summary.FileIssuesByRule[$ruleName] = @{} + } + if (-not $summary.FileIssuesByRule[$ruleName].ContainsKey($fileExtension)) { + $summary.FileIssuesByRule[$ruleName][$fileExtension] = 0 + } + $summary.FileIssuesByRule[$ruleName][$fileExtension]++ + + # Track overall file extension summary + if (-not $summary.FileExtensionSummary.ContainsKey($fileExtension)) { + $summary.FileExtensionSummary[$fileExtension] = @{ + Count = 0 + Rules = @{} + Categories = @{} + } + } + $summary.FileExtensionSummary[$fileExtension].Count++ + + # Track which rules affect this extension + if (-not $summary.FileExtensionSummary[$fileExtension].Rules.ContainsKey($ruleName)) { + $summary.FileExtensionSummary[$fileExtension].Rules[$ruleName] = 0 + } + $summary.FileExtensionSummary[$fileExtension].Rules[$ruleName]++ + + # Track which categories this extension appears in + if (-not $summary.FileExtensionSummary[$fileExtension].Categories.ContainsKey($categoryName)) { + $summary.FileExtensionSummary[$fileExtension].Categories[$categoryName] = 0 + } + $summary.FileExtensionSummary[$fileExtension].Categories[$categoryName]++ + } + } + + # Store item details for detailed view + $summary.Categories[$categoryName].Items += @{ + Identity = $item["Identity"] + Analysis = $item["Analysis"] + Rules = $item["Rules"] + Compare = $item["Compare"] + } + } + } + + # Calculate timespan if we have both run IDs + if ($summary.Metadata.BaseRunId -and $summary.Metadata.CompareRunId) { + try { + $baseTime = [DateTime]::Parse($summary.Metadata.BaseRunId) + $compareTime = [DateTime]::Parse($summary.Metadata.CompareRunId) + $summary.TimeSpan = $compareTime - $baseTime + } + catch { + $summary.TimeSpan = "Unable to calculate" + } + } + + return $summary +} + +function Write-ConsoleSummary { + param( + [Parameter(Mandatory)] + [hashtable]$Summary, + + [Parameter()] + [switch]$ShowDetails, + + [Parameter()] + [switch]$IncludeInformationalEvent, + + [Parameter()] + [switch]$IncludeDebugEvent + ) + + # Header + Write-Host ("=" * 80) -ForegroundColor Cyan + Write-Host "Attack Surface Analyzer Results Summary" -ForegroundColor Cyan + Write-Host ("=" * 80) -ForegroundColor Cyan + Write-Host "" + + # Metadata + Write-Host "Analysis Metadata:" -ForegroundColor Yellow + Write-Host " ASA Version: $($Summary.Metadata.Version)" -ForegroundColor White + Write-Host " Operating System: $($Summary.Metadata.OS) ($($Summary.Metadata.OSVersion))" -ForegroundColor White + if ($Summary.TimeSpan -and $Summary.TimeSpan -ne "Unable to calculate") { + Write-Host " Analysis Duration: $($Summary.TimeSpan.ToString())" -ForegroundColor White + } + Write-Host "" + + # Overall Statistics + Write-Host "Overall Statistics:" -ForegroundColor Yellow + Write-Host " Total Findings: $($Summary.TotalFindings)" -ForegroundColor White + + # Show filtering information + $filterInfo = @() + if (-not $IncludeInformationalEvent) { $filterInfo += "INFORMATION events excluded" } + if (-not $IncludeDebugEvent) { $filterInfo += "DEBUG events excluded" } + if ($filterInfo.Count -gt 0) { + Write-Host " Filtering: $($filterInfo -join ', ')" -ForegroundColor DarkYellow + } + + # Analysis Levels + Write-Host " Analysis Levels:" -ForegroundColor White + foreach ($level in $Summary.AnalysisLevels.Keys | Sort-Object) { + $count = $Summary.AnalysisLevels[$level] + $color = switch ($level) { + 'ERROR' { 'Red' } + 'WARNING' { 'Yellow' } + 'INFORMATION' { 'Green' } + 'DEBUG' { 'DarkGray' } + default { 'White' } + } + Write-Host " $level`: $count" -ForegroundColor $color + } + Write-Host "" + + # Category Breakdown + Write-Host "Findings by Category:" -ForegroundColor Yellow + $sortedCategories = $Summary.Categories.GetEnumerator() | Sort-Object { $_.Value.Count } -Descending + + foreach ($category in $sortedCategories) { + $categoryName = $category.Key + $count = $category.Value.Count + + if ($count -gt 0) { + Write-Host " $categoryName`: $count items" -ForegroundColor Cyan + } + else { + Write-Host " $categoryName`: $count items" -ForegroundColor DarkGray + } + } + Write-Host "" + + # Rule Types Summary + Write-Host "Top Security Rules Triggered:" -ForegroundColor Yellow + $topRules = $Summary.RuleTypes.GetEnumerator() | + Sort-Object { $_.Value.Count } -Descending | + Select-Object -First 10 + + foreach ($rule in $topRules) { + $ruleName = $rule.Key + $count = $rule.Value.Count + $flag = $rule.Value.Flag + + $color = switch ($flag) { + 'ERROR' { 'Red' } + 'WARNING' { 'Yellow' } + 'INFORMATION' { 'Green' } + 'DEBUG' { 'DarkGray' } + default { 'White' } + } + + Write-Host " [$flag] $ruleName`: $count occurrences" -ForegroundColor $color + if ($ShowDetails) { + Write-Host " Description: $($rule.Value.Description)" -ForegroundColor DarkGray + Write-Host " Platforms: $($rule.Value.Platforms -join ', ')" -ForegroundColor DarkGray + + # Show breakdown by category for this rule + if ($rule.Value.Categories.Count -gt 0) { + Write-Host " Categories:" -ForegroundColor DarkGray + foreach ($cat in $rule.Value.Categories.GetEnumerator() | Sort-Object { $_.Value } -Descending) { + Write-Host " $($cat.Key): $($cat.Value) occurrences" -ForegroundColor Gray + } + } + } + } + + # File Extension Summary + if ($Summary.FileExtensionSummary.Count -gt 0) { + Write-Host "" + Write-Host "File Extension Analysis:" -ForegroundColor Yellow + + $sortedExtensions = $Summary.FileExtensionSummary.GetEnumerator() | + Sort-Object { $_.Value.Count } -Descending | + Select-Object -First 15 + + foreach ($extEntry in $sortedExtensions) { + $extension = $extEntry.Key + $count = $extEntry.Value.Count + $displayExt = if ($extension -eq "(no extension)") { $extension } else { "*$extension" } + + Write-Host " $displayExt`: $count files" -ForegroundColor Cyan + + if ($ShowDetails) { + # Show top rules for this extension + $topRulesForExt = $extEntry.Value.Rules.GetEnumerator() | + Sort-Object { $_.Value } -Descending | + Select-Object -First 3 + + foreach ($ruleEntry in $topRulesForExt) { + $ruleName = $ruleEntry.Key + $ruleCount = $ruleEntry.Value + Write-Host " $ruleName`: $ruleCount files" -ForegroundColor Gray + } + } + } + } + + # Detailed Rule Analysis by Category + if ($ShowDetails) { + Write-Host "" + Write-Host "Detailed Rule Analysis by Category:" -ForegroundColor Yellow + + # Focus on file-related categories + $fileCategories = $Summary.Categories.GetEnumerator() | Where-Object { $_.Key -like "*FILE*" -and $_.Value.Count -gt 0 } + + foreach ($category in $fileCategories) { + $categoryName = $category.Key + Write-Host "" + Write-Host " $categoryName Rules Breakdown:" -ForegroundColor Cyan + + # Get rules that appear in this category + $categoryRules = $Summary.RuleTypes.GetEnumerator() | + Where-Object { $_.Value.Categories.ContainsKey($categoryName) } | + Sort-Object { $_.Value.Categories[$categoryName] } -Descending + + foreach ($ruleEntry in $categoryRules) { + $ruleName = $ruleEntry.Key + $count = $ruleEntry.Value.Categories[$categoryName] + $flag = $ruleEntry.Value.Flag + + $color = switch ($flag) { + 'ERROR' { 'Red' } + 'WARNING' { 'Yellow' } + 'INFORMATION' { 'Green' } + 'DEBUG' { 'DarkGray' } + default { 'White' } + } + + Write-Host " [$flag] $ruleName`: $count files" -ForegroundColor $color + } + } + + # Show file extension breakdown if available + if ($Summary.FileIssuesByRule.Count -gt 0) { + Write-Host "" + Write-Host "File Issues by Rule and Extension:" -ForegroundColor Yellow + + foreach ($ruleEntry in $Summary.FileIssuesByRule.GetEnumerator()) { + $ruleName = $ruleEntry.Key + Write-Host "" + Write-Host " $ruleName`:" -ForegroundColor Cyan + + $sortedExtensions = $ruleEntry.Value.GetEnumerator() | Sort-Object { $_.Value } -Descending + foreach ($extEntry in $sortedExtensions) { + $extension = $extEntry.Key + $count = $extEntry.Value + Write-Host " $extension`: $count files" -ForegroundColor White + } + } + } + } + + # Detailed Category Information + if ($ShowDetails) { + Write-Host "" + Write-Host "Detailed Category Breakdown:" -ForegroundColor Yellow + + foreach ($category in $sortedCategories | Where-Object { $_.Value.Count -gt 0 }) { + $categoryName = $category.Key + $items = $category.Value.Items + + Write-Host "" + Write-Host " $categoryName ($($items.Count) items):" -ForegroundColor Cyan + + # Group by analysis level + $groupedByAnalysis = $items | Group-Object Analysis + foreach ($group in $groupedByAnalysis) { + $level = $group.Name + $count = $group.Count + + $color = switch ($level) { + 'ERROR' { 'Red' } + 'WARNING' { 'Yellow' } + 'INFORMATION' { 'Green' } + 'DEBUG' { 'DarkGray' } + default { 'White' } + } + + Write-Host " $level`: $count items" -ForegroundColor $color + } + + # Show individual file details for file-related categories + if ($categoryName -like "*FILE*" -and $items.Count -gt 0) { + # Check if this category contains files with expired signatures + $expiredSigItems = $items | Where-Object { + $_.Rules -and ($_.Rules | Where-Object { $_.Name -eq 'Binaries with expired signatures' }) + } + + if ($expiredSigItems.Count -gt 0) { + Write-Host "" + Write-Host " Files with Expired Signatures (grouped by Issuer):" -ForegroundColor DarkCyan + + # Group by issuer only + $groupedByIssuer = @{} + foreach ($item in $expiredSigItems) { + if ($item.Compare -and $item.Compare.SignatureStatus -and $item.Compare.SignatureStatus.SigningCertificate) { + $cert = $item.Compare.SignatureStatus.SigningCertificate + $issuer = $cert.Issuer + $notAfter = $cert.NotAfter + $identity = $item.Identity + + if (-not $groupedByIssuer.ContainsKey($issuer)) { + $groupedByIssuer[$issuer] = @() + } + $groupedByIssuer[$issuer] += [PSCustomObject]@{ + Identity = $identity + NotAfter = $notAfter + } + } + } + + # Display grouped results + $sortedIssuers = $groupedByIssuer.GetEnumerator() | Sort-Object Name + + foreach ($issuerGroup in $sortedIssuers) { + $issuer = $issuerGroup.Name + $files = $issuerGroup.Value + $fileCount = $files.Count + + Write-Host "" + Write-Host " Issuer: $issuer" -ForegroundColor Yellow + Write-Host " Files ($fileCount):" -ForegroundColor White + + # Sort files by full file path + $sortedFiles = $files | Sort-Object Identity + + # Show all files + foreach ($file in $sortedFiles) { + # Get identity - handle both hashtable and PSCustomObject + $filePath = if ($file -is [hashtable]) { $file['Identity'] } else { $file.Identity } + + # Format date without time + $expirationDate = 'Unknown' + $notAfterValue = if ($file -is [hashtable]) { $file['NotAfter'] } else { $file.NotAfter } + if ($notAfterValue) { + try { + $expirationDate = ([DateTime]::Parse($notAfterValue)).ToString('yyyy-MM-dd') + } + catch { + $expirationDate = 'Unknown' + } + } + Write-Host " [Expired: $expirationDate] $filePath" -ForegroundColor Gray + } + } + + # Show other files (non-expired signature issues) + $otherFiles = $items | Where-Object { + -not ($_.Rules -and ($_.Rules | Where-Object { $_.Name -eq 'Binaries with expired signatures' })) + } + + if ($otherFiles.Count -gt 0) { + Write-Host "" + Write-Host " Other Files:" -ForegroundColor DarkCyan + + $displayLimit = [Math]::Min(20, $otherFiles.Count) + for ($i = 0; $i -lt $displayLimit; $i++) { + $item = $otherFiles[$i] + $identity = $item.Identity + $analysis = $item.Analysis + + $color = switch ($analysis) { + 'ERROR' { 'Red' } + 'WARNING' { 'Yellow' } + 'INFORMATION' { 'Green' } + 'DEBUG' { 'DarkGray' } + default { 'Gray' } + } + + if ($item.Rules -and $item.Rules.Count -gt 0) { + $ruleNames = $item.Rules | ForEach-Object { $_.Name } + Write-Host " [$analysis] $identity" -ForegroundColor $color + Write-Host " Rules: $($ruleNames -join ', ')" -ForegroundColor DarkGray + } + else { + Write-Host " [$analysis] $identity" -ForegroundColor $color + } + } + + if ($otherFiles.Count -gt $displayLimit) { + Write-Host " ... and $($otherFiles.Count - $displayLimit) more files" -ForegroundColor DarkGray + } + } + } + else { + # No expired signatures, show standard file listing + Write-Host "" + Write-Host " Files:" -ForegroundColor DarkCyan + + $displayLimit = [Math]::Min(50, $items.Count) + for ($i = 0; $i -lt $displayLimit; $i++) { + $item = $items[$i] + $identity = $item.Identity + $analysis = $item.Analysis + + $color = switch ($analysis) { + 'ERROR' { 'Red' } + 'WARNING' { 'Yellow' } + 'INFORMATION' { 'Green' } + 'DEBUG' { 'DarkGray' } + default { 'Gray' } + } + + # Show triggered rules for this file + if ($item.Rules -and $item.Rules.Count -gt 0) { + $ruleNames = $item.Rules | ForEach-Object { $_.Name } + Write-Host " [$analysis] $identity" -ForegroundColor $color + Write-Host " Rules: $($ruleNames -join ', ')" -ForegroundColor DarkGray + } + else { + Write-Host " [$analysis] $identity" -ForegroundColor $color + } + } + + if ($items.Count -gt $displayLimit) { + Write-Host " ... and $($items.Count - $displayLimit) more files" -ForegroundColor DarkGray + } + } + } + # Show details for non-file categories (users, groups, etc.) + elseif ($items.Count -gt 0) { + Write-Host "" + Write-Host " Items:" -ForegroundColor DarkCyan + + $displayLimit = [Math]::Min(20, $items.Count) + for ($i = 0; $i -lt $displayLimit; $i++) { + $item = $items[$i] + $identity = $item.Identity + $analysis = $item.Analysis + + $color = switch ($analysis) { + 'ERROR' { 'Red' } + 'WARNING' { 'Yellow' } + 'INFORMATION' { 'Green' } + 'DEBUG' { 'DarkGray' } + default { 'Gray' } + } + + Write-Host " [$analysis] $identity" -ForegroundColor $color + } + + if ($items.Count -gt $displayLimit) { + Write-Host " ... and $($items.Count - $displayLimit) more items" -ForegroundColor DarkGray + } + } + } + } + + Write-Host "" + Write-Host ("=" * 80) -ForegroundColor Cyan +} + +# Main execution +try { + # Validate input file + if (-not (Test-Path $Path)) { + Write-Error "ASA results file not found: $Path" + exit 1 + } + + Write-Verbose "Reading ASA results from: $Path" + + # Load and parse JSON + $jsonContent = Get-Content -Path $Path -Raw -Encoding UTF8 + $asaData = $jsonContent | ConvertFrom-Json -AsHashtable + + # Generate summary + Write-Verbose "Analyzing ASA results..." + $summary = Get-AsaSummary -AsaData $asaData -IncludeInformationalEvent:$IncludeInformationalEvent -IncludeDebugEvent:$IncludeDebugEvent + + # Output results to console + Write-ConsoleSummary -Summary $summary -ShowDetails:$ShowDetails -IncludeInformationalEvent:$IncludeInformationalEvent -IncludeDebugEvent:$IncludeDebugEvent +} +catch { + Write-Error "Error processing ASA results: $($_.Exception.Message)" + Write-Error $_.ScriptStackTrace + exit 1 +} diff --git a/tools/AttackSurfaceAnalyzer/docker/Dockerfile b/tools/AttackSurfaceAnalyzer/docker/Dockerfile new file mode 100644 index 00000000000..3e4aaa3b717 --- /dev/null +++ b/tools/AttackSurfaceAnalyzer/docker/Dockerfile @@ -0,0 +1,134 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# Multi-stage Dockerfile for Attack Surface Analyzer Testing +# Stage 1: Build and run ASA tests +# Stage 2: Extract reports to scratch layer + +# Stage 1: Test execution environment +FROM mcr.microsoft.com/dotnet/sdk:9.0-windowsservercore-ltsc2022@sha256:28f3a59216a7f91dfc4730ea47e236e2ffbb519975725bf8231f57e69dab3ca8 AS asa-runner + +# Set shell to PowerShell for easier scripting +SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"] + +# Install Attack Surface Analyzer as a global .NET tool +RUN dotnet tool install -g Microsoft.CST.AttackSurfaceAnalyzer.CLI --version 2.3.328 + +# Add .NET tools directory to PATH +RUN $env:PATH += ';C:/Users/ContainerAdministrator/.dotnet/tools'; \ + [Environment]::SetEnvironmentVariable('PATH', $env:PATH, [EnvironmentVariableTarget]::Machine) + +# Set working directory and create reports directory +WORKDIR C:/work +RUN New-Item -ItemType Directory -Path C:\reports -Force | Out-Null + +# Take baseline snapshot before installation +RUN Write-Host "=========================================" -ForegroundColor Green; \ + Write-Host "Taking baseline snapshot..." -ForegroundColor Green; \ + Write-Host "========================================="; \ + asa collect -f -r -u -l --directories 'C:\Program Files\PowerShell,C:\Program Files (x86)\PowerShell' --runid before; \ + if ($LASTEXITCODE -ne 0) { Write-Error "Failed to take baseline snapshot"; exit 1 } + +# Copy the PowerShell MSI file from build context +COPY *.msi ./powershell.msi + +# Install PowerShell MSI +RUN Write-Host "=========================================" -ForegroundColor Green; \ + Write-Host "Installing PowerShell MSI..." -ForegroundColor Green; \ + Write-Host "========================================="; \ + Write-Host "MSI file: C:\work\powershell.msi"; \ + $argumentList = '/i C:\work\powershell.msi /quiet /norestart /l*vx C:\work\install.log ADD_PATH=1'; \ + Write-Host "Running: msiexec $argumentList"; \ + $msiProcess = Start-Process msiexec.exe -ArgumentList $argumentList -Wait -NoNewWindow -PassThru; \ + if ($msiProcess.ExitCode -ne 0) { \ + Write-Host "MSI installation failed with exit code: $($msiProcess.ExitCode)"; \ + throw "MSI installation failed. Check install.log for details" \ + } + +# Take post-installation snapshot +RUN Write-Host "=========================================" -ForegroundColor Green; \ + Write-Host "Taking post-installation snapshot..." -ForegroundColor Green; \ + Write-Host "========================================="; \ + asa collect -f -r -u -l --directories 'C:\Program Files\PowerShell,C:\Program Files (x86)\PowerShell' --runid after; \ + if ($LASTEXITCODE -ne 0) { Write-Error "Failed to take post-installation snapshot"; exit 1 } + +# Export comparison results +RUN Write-Host "=========================================" -ForegroundColor Green; \ + Write-Host "Exporting comparison results..." -ForegroundColor Green; \ + Write-Host "========================================="; \ + asa export-collect --savetodatabase --resultlevels WARNING,ERROR,FATAL --firstrunid before --secondrunid after; \ + if ($LASTEXITCODE -ne 0) { Write-Warning "Failed to export results with exit code: $LASTEXITCODE" } + +# Export comparison results +RUN Write-Host "=========================================" -ForegroundColor Green; \ + Write-Host "Exporting comparison results..." -ForegroundColor Green; \ + Write-Host "========================================="; \ + asa export-collect --readfromsavedcomparisons; \ + if ($LASTEXITCODE -ne 0) { Write-Warning "Failed to export results with exit code: $LASTEXITCODE" } + + +# Copy and standardize JSON result files +RUN Write-Host "=========================================" -ForegroundColor Green; \ + Write-Host "Processing JSON result files..." -ForegroundColor Green; \ + Write-Host "========================================="; \ + $jsonFiles = Get-ChildItem -Path "*.json.txt" -ErrorAction SilentlyContinue; \ + if ($jsonFiles.Count -eq 0) { \ + Write-Warning 'No JSON.TXT files found - checking for .json files...'; \ + $jsonFiles = Get-ChildItem -Path "*.json" -ErrorAction SilentlyContinue \ + }; \ + if ($jsonFiles.Count -eq 0) { \ + throw 'No JSON files found - ASA may not have generated results' \ + } else { \ + $jsonFiles | ForEach-Object { \ + Write-Host "Found JSON file: $($_.Name)"; \ + if (-not (Test-Path $_.FullName)) { \ + throw "JSON file not accessible: $($_.FullName)" \ + }; \ + Write-Host "Copying to standard name: asa-results.json"; \ + Copy-Item -Path $_.FullName -Destination C:\work\asa-results.json -ErrorAction Stop; \ + Copy-Item -Path $_.FullName -Destination C:\reports\asa-results.json -ErrorAction Stop; \ + } \ + } + +# Copy SQLite database file if it exists +RUN Write-Host "=========================================" -ForegroundColor Green; \ + Write-Host "Copying SQLite database..." -ForegroundColor Green; \ + Write-Host "========================================="; \ + if (Test-Path "asa.sqlite") { \ + Write-Host "Copying: asa.sqlite"; \ + Copy-Item -Path "asa.sqlite" -Destination C:\reports\ -ErrorAction Stop \ + } else { \ + throw 'SQLite database (asa.sqlite) not found - this may indicate ASA export issues' \ + } + +# Copy installation log file +RUN Write-Host "=========================================" -ForegroundColor Green; \ + Write-Host "Copying installation log..." -ForegroundColor Green; \ + Write-Host "========================================="; \ + if (-not (Test-Path "C:\work\install.log")) { \ + throw "Required installation log file not found: C:\work\install.log" \ + }; \ + Copy-Item -Path "C:\work\install.log" -Destination C:\reports\ -ErrorAction Stop; \ + Write-Host "Attack Surface Analyzer test completed!" -ForegroundColor Green + +# Default command shows completion message +CMD Write-Host "=========================================" -ForegroundColor Green; \ + Write-Host "Container ready. Reports available in C:\reports\" -ForegroundColor Cyan; \ + Write-Host "=========================================" + +# Stage 2: Reports-only layer using minimal Windows base +FROM mcr.microsoft.com/windows/nanoserver:ltsc2022@sha256:307874138e4dc064d0538b58c6f028419ab82fb15fcabaf6d5378ba32c235266 AS asa-reports + +# Set working directory to root +WORKDIR / + +# Copy only the report files from the runner stage to root level +COPY --from=asa-runner C:/reports/ ./ + +# Stage 3: Final stage (defaults to the runner for backward compatibility) +FROM asa-runner AS final + +# Label for documentation +LABEL description="Windows container for running Attack Surface Analyzer tests on PowerShell MSI installations" +LABEL version="1.0" +LABEL maintainer="PowerShell Team" diff --git a/tools/UpdateDotnetRuntime.ps1 b/tools/UpdateDotnetRuntime.ps1 index 339153bcf0c..f62a16cfe14 100644 --- a/tools/UpdateDotnetRuntime.ps1 +++ b/tools/UpdateDotnetRuntime.ps1 @@ -9,9 +9,6 @@ param ( [Parameter()] [switch]$UseNuGetOrg, - [Parameter()] - [switch]$UpdateMSIPackaging, - [Parameter()] [string]$RuntimeSourceFeed, @@ -366,31 +363,6 @@ if ($dotnetUpdate.ShouldUpdate) { Write-Verbose -Message "Updating project files completed." -Verbose - if ($UpdateMSIPackaging) { - if (-not $environment.IsWindows) { - throw "UpdateMSIPackaging can only be done on Windows" - } - - Import-Module "$PSScriptRoot/../build.psm1" -Force - Import-Module "$PSScriptRoot/packaging" -Force - Start-PSBootstrap -Package - Start-PSBuild -Clean -Configuration Release -InteractiveAuth:$InteractiveAuth - - $publishPath = Split-Path (Get-PSOutput) - Remove-Item -Path "$publishPath\*.pdb" - - try { - Start-PSPackage -Type msi -SkipReleaseChecks -InformationVariable wxsData - } catch { - if ($_.Exception.Message -like "Current files to not match *") { - Copy-Item -Path $($wxsData.MessageData.NewFile) -Destination ($wxsData.MessageData.FilesWxsPath) - Write-Verbose -Message "Updating files.wxs file completed." -Verbose - } else { - throw $_ - } - } - } - Update-DevContainer } else { diff --git a/tools/WindowsCI.psm1 b/tools/WindowsCI.psm1 index 57d506bda8b..685882546c2 100644 --- a/tools/WindowsCI.psm1 +++ b/tools/WindowsCI.psm1 @@ -15,6 +15,8 @@ function New-LocalUser .OUTPUTS .NOTES #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingUsernameAndPasswordParams', '')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')] param( [Parameter(Mandatory=$true)] [string] $username, diff --git a/tools/buildCommon/startNativeExecution.ps1 b/tools/buildCommon/startNativeExecution.ps1 index ee7b00d04cd..2e44d86bd6a 100644 --- a/tools/buildCommon/startNativeExecution.ps1 +++ b/tools/buildCommon/startNativeExecution.ps1 @@ -16,6 +16,8 @@ function script:Start-NativeExecution { $ErrorActionPreference = "Continue" Write-Verbose "Executing: $ScriptBlock" try { + $cwd = Get-Location + if ($VerboseOutputOnError.IsPresent) { $output = & $ScriptBlock 2>&1 } else { @@ -36,10 +38,10 @@ function script:Start-NativeExecution { $callerFile = $callerLocationParts[0] $callerLine = $callerLocationParts[1] - $errorMessage = "Execution of {$ScriptBlock} by ${callerFile}: line $callerLine failed with exit code $LASTEXITCODE" + $errorMessage = "Execution of {$ScriptBlock} in '$cwd' by ${callerFile}: line $callerLine failed with exit code $LASTEXITCODE" throw $errorMessage } - throw "Execution of {$ScriptBlock} failed with exit code $LASTEXITCODE" + throw "Execution of {$ScriptBlock} in '$cwd' failed with exit code $LASTEXITCODE" } } finally { $ErrorActionPreference = $backupEAP diff --git a/tools/cgmanifest/main/cgmanifest.json b/tools/cgmanifest/main/cgmanifest.json new file mode 100644 index 00000000000..5d2de074cae --- /dev/null +++ b/tools/cgmanifest/main/cgmanifest.json @@ -0,0 +1,755 @@ +{ + "Registrations": [ + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "DotNetAnalyzers.DocumentationAnalyzers.Unstable", + "Version": "1.0.0.59" + } + }, + "DevelopmentDependency": true + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "DotNetAnalyzers.DocumentationAnalyzers", + "Version": "1.0.0-beta.59" + } + }, + "DevelopmentDependency": true + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "Humanizer.Core", + "Version": "2.14.1" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "Json.More.Net", + "Version": "2.1.1" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "JsonPointer.Net", + "Version": "5.3.1" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "JsonSchema.Net", + "Version": "7.4.0" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "Markdig.Signed", + "Version": "1.1.2" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "Microsoft.ApplicationInsights", + "Version": "2.23.0" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "Microsoft.Bcl.AsyncInterfaces", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "Microsoft.CodeAnalysis.Analyzers", + "Version": "3.11.0" + } + }, + "DevelopmentDependency": true + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "Microsoft.CodeAnalysis.Common", + "Version": "5.0.0" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "Microsoft.CodeAnalysis.CSharp", + "Version": "5.0.0" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "Microsoft.Extensions.ObjectPool", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "Microsoft.Management.Infrastructure.Runtime.Win", + "Version": "3.0.0" + } + }, + "DevelopmentDependency": true + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "Microsoft.PowerShell.MarkdownRender", + "Version": "7.2.1" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "Microsoft.Security.Extensions", + "Version": "1.4.0" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "Microsoft.Win32.Registry.AccessControl", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "Microsoft.Win32.SystemEvents", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "Microsoft.Windows.Compatibility", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "Newtonsoft.Json", + "Version": "13.0.4" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.android-arm.runtime.native.System.IO.Ports", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.android-arm64.runtime.native.System.IO.Ports", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.android-x64.runtime.native.System.IO.Ports", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.android-x86.runtime.native.System.IO.Ports", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.linux-arm.runtime.native.System.IO.Ports", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.linux-arm64.runtime.native.System.IO.Ports", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.linux-bionic-arm64.runtime.native.System.IO.Ports", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.linux-bionic-x64.runtime.native.System.IO.Ports", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.linux-musl-arm.runtime.native.System.IO.Ports", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.linux-musl-arm64.runtime.native.System.IO.Ports", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.linux-musl-x64.runtime.native.System.IO.Ports", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.linux-x64.runtime.native.System.IO.Ports", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.maccatalyst-arm64.runtime.native.System.IO.Ports", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.maccatalyst-x64.runtime.native.System.IO.Ports", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.native.System.Data.SqlClient.sni", + "Version": "4.4.0" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.native.System.IO.Ports", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.osx-arm64.runtime.native.System.IO.Ports", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.osx-x64.runtime.native.System.IO.Ports", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.win-arm64.runtime.native.System.Data.SqlClient.sni", + "Version": "4.4.0" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.win-x64.runtime.native.System.Data.SqlClient.sni", + "Version": "4.4.0" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.win-x86.runtime.native.System.Data.SqlClient.sni", + "Version": "4.4.0" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "StyleCop.Analyzers.Unstable", + "Version": "1.2.0.556" + } + }, + "DevelopmentDependency": true + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "StyleCop.Analyzers", + "Version": "1.1.118" + } + }, + "DevelopmentDependency": true + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.CodeDom", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.ComponentModel.Composition.Registration", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.ComponentModel.Composition", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Configuration.ConfigurationManager", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Data.Odbc", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Data.OleDb", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Data.SqlClient", + "Version": "4.9.1" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Diagnostics.EventLog", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Diagnostics.PerformanceCounter", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.DirectoryServices.AccountManagement", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.DirectoryServices.Protocols", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.DirectoryServices", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Drawing.Common", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.IO.Packaging", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.IO.Ports", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Management", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Net.Http.WinHttpHandler", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Reflection.Context", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Runtime.Caching", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Security.Cryptography.Pkcs", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Security.Cryptography.ProtectedData", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Security.Cryptography.Xml", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Security.Permissions", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.ServiceModel.Http", + "Version": "10.0.652802" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.ServiceModel.NetFramingBase", + "Version": "10.0.652802" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.ServiceModel.NetTcp", + "Version": "10.0.652802" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.ServiceModel.Primitives", + "Version": "10.0.652802" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.ServiceModel.Syndication", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.ServiceProcess.ServiceController", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Speech", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Web.Services.Description", + "Version": "8.1.2" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Windows.Extensions", + "Version": "10.0.6" + } + }, + "DevelopmentDependency": false + } + ], + "$schema": "https://json.schemastore.org/component-detection-manifest.json" +} diff --git a/tools/cgmanifest.json b/tools/cgmanifest/tpn/cgmanifest.json similarity index 86% rename from tools/cgmanifest.json rename to tools/cgmanifest/tpn/cgmanifest.json index 57c44ca752e..a0746028a56 100644 --- a/tools/cgmanifest.json +++ b/tools/cgmanifest/tpn/cgmanifest.json @@ -65,7 +65,7 @@ "Type": "nuget", "Nuget": { "Name": "Markdig.Signed", - "Version": "0.41.3" + "Version": "0.45.0" } }, "DevelopmentDependency": false @@ -85,7 +85,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Bcl.AsyncInterfaces", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -105,7 +105,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.CodeAnalysis.Common", - "Version": "4.14.0" + "Version": "5.0.0" } }, "DevelopmentDependency": false @@ -115,7 +115,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.CodeAnalysis.CSharp", - "Version": "4.14.0" + "Version": "5.0.0" } }, "DevelopmentDependency": false @@ -125,7 +125,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Extensions.ObjectPool", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -165,7 +165,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Win32.Registry.AccessControl", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -175,7 +175,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Win32.SystemEvents", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -185,7 +185,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Windows.Compatibility", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -195,7 +195,7 @@ "Type": "nuget", "Nuget": { "Name": "Newtonsoft.Json", - "Version": "13.0.3" + "Version": "13.0.4" } }, "DevelopmentDependency": false @@ -205,7 +205,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-arm.runtime.native.System.IO.Ports", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -215,7 +215,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-arm64.runtime.native.System.IO.Ports", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -225,7 +225,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-x64.runtime.native.System.IO.Ports", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -235,7 +235,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-x86.runtime.native.System.IO.Ports", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -245,7 +245,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-arm.runtime.native.System.IO.Ports", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -255,7 +255,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-arm64.runtime.native.System.IO.Ports", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -265,7 +265,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-bionic-arm64.runtime.native.System.IO.Ports", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -275,7 +275,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-bionic-x64.runtime.native.System.IO.Ports", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -285,7 +285,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-musl-arm.runtime.native.System.IO.Ports", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -295,7 +295,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-musl-arm64.runtime.native.System.IO.Ports", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -305,7 +305,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-musl-x64.runtime.native.System.IO.Ports", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -315,7 +315,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-x64.runtime.native.System.IO.Ports", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -325,7 +325,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.maccatalyst-arm64.runtime.native.System.IO.Ports", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -335,7 +335,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.maccatalyst-x64.runtime.native.System.IO.Ports", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -355,7 +355,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.native.System.IO.Ports", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -365,7 +365,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.osx-arm64.runtime.native.System.IO.Ports", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -375,7 +375,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.osx-x64.runtime.native.System.IO.Ports", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -435,7 +435,7 @@ "Type": "nuget", "Nuget": { "Name": "System.CodeDom", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -445,7 +445,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ComponentModel.Composition.Registration", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -455,7 +455,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ComponentModel.Composition", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -465,7 +465,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Configuration.ConfigurationManager", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -475,7 +475,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Data.Odbc", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -485,7 +485,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Data.OleDb", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -505,7 +505,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Diagnostics.EventLog", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -515,7 +515,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Diagnostics.PerformanceCounter", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -525,7 +525,7 @@ "Type": "nuget", "Nuget": { "Name": "System.DirectoryServices.AccountManagement", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -535,7 +535,7 @@ "Type": "nuget", "Nuget": { "Name": "System.DirectoryServices.Protocols", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -545,7 +545,7 @@ "Type": "nuget", "Nuget": { "Name": "System.DirectoryServices", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -555,7 +555,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Drawing.Common", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -565,7 +565,7 @@ "Type": "nuget", "Nuget": { "Name": "System.IO.Packaging", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -575,7 +575,7 @@ "Type": "nuget", "Nuget": { "Name": "System.IO.Ports", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -585,7 +585,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Management", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -595,17 +595,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Net.Http.WinHttpHandler", - "Version": "9.0.8" - } - }, - "DevelopmentDependency": false - }, - { - "Component": { - "Type": "nuget", - "Nuget": { - "Name": "System.Private.ServiceModel", - "Version": "4.10.3" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -615,7 +605,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Reflection.Context", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -625,7 +615,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Runtime.Caching", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -635,7 +625,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Cryptography.Pkcs", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -645,7 +635,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Cryptography.ProtectedData", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -655,7 +645,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Cryptography.Xml", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -665,7 +655,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Permissions", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -674,8 +664,8 @@ "Component": { "Type": "nuget", "Nuget": { - "Name": "System.ServiceModel.Duplex", - "Version": "4.10.3" + "Name": "System.ServiceModel.Http", + "Version": "10.0.652802" } }, "DevelopmentDependency": false @@ -684,8 +674,8 @@ "Component": { "Type": "nuget", "Nuget": { - "Name": "System.ServiceModel.Http", - "Version": "4.10.3" + "Name": "System.ServiceModel.NetFramingBase", + "Version": "10.0.652802" } }, "DevelopmentDependency": false @@ -695,7 +685,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ServiceModel.NetTcp", - "Version": "4.10.3" + "Version": "10.0.652802" } }, "DevelopmentDependency": false @@ -705,17 +695,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ServiceModel.Primitives", - "Version": "4.10.3" - } - }, - "DevelopmentDependency": false - }, - { - "Component": { - "Type": "nuget", - "Nuget": { - "Name": "System.ServiceModel.Security", - "Version": "4.10.3" + "Version": "10.0.652802" } }, "DevelopmentDependency": false @@ -725,7 +705,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ServiceModel.Syndication", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -735,7 +715,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ServiceProcess.ServiceController", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -745,17 +725,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Speech", - "Version": "9.0.8" - } - }, - "DevelopmentDependency": false - }, - { - "Component": { - "Type": "nuget", - "Nuget": { - "Name": "System.Threading.AccessControl", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -775,7 +745,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Windows.Extensions", - "Version": "9.0.8" + "Version": "10.0.3" } }, "DevelopmentDependency": false diff --git a/tools/ci.psm1 b/tools/ci.psm1 index 317f05effd0..33700ac9024 100644 --- a/tools/ci.psm1 +++ b/tools/ci.psm1 @@ -1,6 +1,9 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. +[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '')] +param() + Set-StrictMode -Version 3.0 $ErrorActionPreference = 'continue' @@ -98,6 +101,11 @@ function Invoke-CIFull # Implements the CI 'build_script' step function Invoke-CIBuild { + param( + [ValidateSet('Debug', 'Release', 'CodeCoverage', 'StaticAnalysis')] + [string]$Configuration = 'Release' + ) + $releaseTag = Get-ReleaseTag # check to be sure our test tags are correct $result = Get-PesterTag @@ -112,7 +120,7 @@ function Invoke-CIBuild Start-PSBuild -Configuration 'CodeCoverage' -PSModuleRestore -CI -ReleaseTag $releaseTag } - Start-PSBuild -PSModuleRestore -Configuration 'Release' -CI -ReleaseTag $releaseTag -UseNuGetOrg + Start-PSBuild -PSModuleRestore -Configuration $Configuration -CI -ReleaseTag $releaseTag -UseNuGetOrg Save-PSOptions $options = (Get-PSOptions) @@ -220,6 +228,45 @@ function Invoke-CIxUnit } } +# Install Pester module if not already installed with a compatible version +function Install-CIPester +{ + [CmdletBinding()] + param( + [string]$MinimumVersion = '5.0.0', + [string]$MaximumVersion = '5.99.99', + [switch]$Force + ) + + Write-Verbose "Checking for Pester module (required: $MinimumVersion - $MaximumVersion)" -Verbose + + # Check if a compatible version of Pester is already installed + $installedPester = Get-Module -Name Pester -ListAvailable | + Where-Object { $_.Version -ge $MinimumVersion -and $_.Version -le $MaximumVersion } | + Sort-Object -Property Version -Descending | + Select-Object -First 1 + + if ($installedPester -and -not $Force) { + Write-Host "Pester version $($installedPester.Version) is already installed and meets requirements" -ForegroundColor Green + return + } + + if ($Force) { + Write-Host "Installing Pester module (forced)" -ForegroundColor Yellow + } else { + Write-Host "Installing Pester module" -ForegroundColor Yellow + } + + try { + Install-Module -Name Pester -Force -SkipPublisherCheck -MaximumVersion $MaximumVersion -ErrorAction Stop + Write-Host "Successfully installed Pester module" -ForegroundColor Green + } + catch { + Write-Error "Failed to install Pester module: $_" + throw + } +} + # Implement CI 'Test_script' function Invoke-CITest { @@ -569,20 +616,9 @@ function Invoke-CIFinish Restore-PSOptions -PSOptionsPath "${buildFolder}/psoptions.json" $preReleaseVersion = $env:CI_FINISH_RELASETAG - # Build packages $preReleaseVersion = "$previewPrefix-$previewLabel.$prereleaseIteration" - switch -regex ($Runtime){ - default { - $runPackageTest = $true - $packageTypes = 'msi', 'zip', 'zip-pdb', 'msix' - } - 'win-arm.*' { - $runPackageTest = $false - $packageTypes = 'msi', 'zip', 'zip-pdb', 'msix' - } - } + # Build packages + $packageTypes = 'zip', 'zip-pdb', 'msix' - Import-Module "$PSScriptRoot\wix\wix.psm1" - Install-Wix -arm64:$true $packages = Start-PSPackage -Type $packageTypes -ReleaseTag $preReleaseVersion -SkipReleaseChecks -WindowsRuntime $Runtime foreach ($package in $packages) { @@ -594,40 +630,9 @@ function Invoke-CIFinish if ($package -is [string]) { $null = $artifacts.Add($package) - } elseif ($package -is [pscustomobject] -and $package.psobject.Properties['msi']) { - $null = $artifacts.Add($package.msi) - $null = $artifacts.Add($package.wixpdb) } } - if ($runPackageTest) { - # the packaging tests find the MSI package using env:PSMsiX64Path - $env:PSMsiX64Path = $artifacts | Where-Object { $_.EndsWith(".msi")} - $architechture = $Runtime.Split('-')[1] - $exePath = New-ExePackage -ProductVersion ($preReleaseVersion -replace '^v') -ProductTargetArchitecture $architechture -MsiLocationPath $env:PSMsiX64Path - Write-Verbose "exe Path: $exePath" -Verbose - $artifacts.Add($exePath) - $env:PSExePath = $exePath - $env:PSMsiChannel = $Channel - $env:PSMsiRuntime = $Runtime - - # Install the latest Pester and import it - $maximumPesterVersion = '4.99' - Install-Module Pester -Force -SkipPublisherCheck -MaximumVersion $maximumPesterVersion - Import-Module Pester -Force -MaximumVersion $maximumPesterVersion - - $testResultPath = Join-Path -Path $env:TEMP -ChildPath "win-package-$channel-$runtime.xml" - - # start the packaging tests and get the results - $packagingTestResult = Invoke-Pester -Script (Join-Path $repoRoot '.\test\packaging\windows\') -PassThru -OutputFormat NUnitXml -OutputFile $testResultPath - - Publish-TestResults -Title "win-package-$channel-$runtime" -Path $testResultPath - - # fail the CI job if the tests failed, or nothing passed - if (-not $packagingTestResult -is [pscustomobject] -or $packagingTestResult.FailedCount -ne 0 -or $packagingTestResult.PassedCount -eq 0) { - throw "Packaging tests failed ($($packagingTestResult.FailedCount) failed/$($packagingTestResult.PassedCount) passed)" - } - } } } catch { Get-Error -InputObject $_ @@ -680,6 +685,14 @@ function Set-Path } } +# Display environment variables in a log group for GitHub Actions +function Show-Environment +{ + Write-LogGroupStart -Title 'Environment' + Get-ChildItem -Path env: | Out-String -width 9999 -Stream | Write-Verbose -Verbose + Write-LogGroupEnd -Title 'Environment' +} + # Bootstrap script for Linux and macOS function Invoke-BootstrapStage { @@ -871,16 +884,36 @@ function New-LinuxPackage $packageObj = $package } - Write-Log -message "Artifacts directory: ${env:BUILD_ARTIFACTSTAGINGDIRECTORY}" - Copy-Item $packageObj.FullName -Destination "${env:BUILD_ARTIFACTSTAGINGDIRECTORY}" -Force + # Determine artifacts directory (GitHub Actions or Azure DevOps) + $artifactsDir = if ($env:GITHUB_ACTIONS -eq 'true') { + "${env:GITHUB_WORKSPACE}/../packages" + } else { + "${env:BUILD_ARTIFACTSTAGINGDIRECTORY}" + } + + # Ensure artifacts directory exists + if (-not (Test-Path $artifactsDir)) { + New-Item -ItemType Directory -Path $artifactsDir -Force | Out-Null + } + + Write-Log -message "Artifacts directory: $artifactsDir" + Copy-Item $packageObj.FullName -Destination $artifactsDir -Force } if ($IsLinux) { + # Determine artifacts directory (GitHub Actions or Azure DevOps) + $artifactsDir = if ($env:GITHUB_ACTIONS -eq 'true') { + "${env:GITHUB_WORKSPACE}/../packages" + } else { + "${env:BUILD_ARTIFACTSTAGINGDIRECTORY}" + } + # Create and package Raspbian .tgz + # Build must be clean for Raspbian Start-PSBuild -PSModuleRestore -Clean -Runtime linux-arm -Configuration 'Release' $armPackage = Start-PSPackage @packageParams -Type tar-arm -SkipReleaseChecks - Copy-Item $armPackage -Destination "${env:BUILD_ARTIFACTSTAGINGDIRECTORY}" -Force + Copy-Item $armPackage -Destination $artifactsDir -Force } } @@ -941,3 +974,226 @@ function Invoke-InitializeContainerStage { Write-Host "##vso[build.addbuildtag]$($selectedImage.JobName)" } } + +Function Test-MergeConflictMarker +{ + <# + .SYNOPSIS + Checks files for Git merge conflict markers and outputs results for GitHub Actions. + .DESCRIPTION + Scans the specified files for Git merge conflict markers (<<<<<<<, =======, >>>>>>>) + and generates console output, GitHub Actions outputs, and job summary. + Designed for use in GitHub Actions workflows. + .PARAMETER File + Array of file paths (relative or absolute) to check for merge conflict markers. + .PARAMETER WorkspacePath + Base workspace path for resolving relative paths. Defaults to current directory. + .PARAMETER OutputPath + Path to write GitHub Actions outputs. Defaults to $env:GITHUB_OUTPUT. + .PARAMETER SummaryPath + Path to write GitHub Actions job summary. Defaults to $env:GITHUB_STEP_SUMMARY. + .EXAMPLE + Test-MergeConflictMarker -File @('file1.txt', 'file2.cs') -WorkspacePath $env:GITHUB_WORKSPACE + #> + [CmdletBinding()] + param( + [Parameter()] + [AllowEmptyCollection()] + [string[]] $File = @(), + + [Parameter()] + [string] $WorkspacePath = $PWD, + + [Parameter()] + [string] $OutputPath = $env:GITHUB_OUTPUT, + + [Parameter()] + [string] $SummaryPath = $env:GITHUB_STEP_SUMMARY + ) + + Write-Host "Starting merge conflict marker check..." -ForegroundColor Cyan + + # Helper function to write outputs when no files to check + function Write-NoFilesOutput { + param( + [string]$Message, + [string]$OutputPath, + [string]$SummaryPath + ) + + # Output results to GitHub Actions + if ($OutputPath) { + "files-checked=0" | Out-File -FilePath $OutputPath -Append -Encoding utf8 + "conflicts-found=0" | Out-File -FilePath $OutputPath -Append -Encoding utf8 + } + + # Create GitHub Actions job summary + if ($SummaryPath) { + $summaryContent = @" +# Merge Conflict Marker Check Results + +## Summary +- **Files Checked:** 0 +- **Files with Conflicts:** 0 + +## ℹ️ No Files to Check + +$Message + +"@ + $summaryContent | Out-File -FilePath $SummaryPath -Encoding utf8 + } + } + + # Handle empty file list (e.g., when PR only deletes files) + if ($File.Count -eq 0) { + Write-Host "No files to check (empty file list)" -ForegroundColor Yellow + Write-NoFilesOutput -Message "No files were provided for checking (this can happen when a PR only deletes files)." -OutputPath $OutputPath -SummaryPath $SummaryPath + return + } + + # Filter out *.cs files from merge conflict checking + $filesToCheck = @($File | Where-Object { $_ -notlike "*.cs" }) + $filteredCount = $File.Count - $filesToCheck.Count + + if ($filteredCount -gt 0) { + Write-Host "Filtered out $filteredCount *.cs file(s) from merge conflict checking" -ForegroundColor Yellow + } + + if ($filesToCheck.Count -eq 0) { + Write-Host "No files to check after filtering (all files were *.cs)" -ForegroundColor Yellow + Write-NoFilesOutput -Message "All $filteredCount file(s) were filtered out (*.cs files are excluded from merge conflict checking)." -OutputPath $OutputPath -SummaryPath $SummaryPath + return + } + + Write-Host "Checking $($filesToCheck.Count) changed files for merge conflict markers" -ForegroundColor Cyan + + # Convert relative paths to absolute paths for processing + $absolutePaths = $filesToCheck | ForEach-Object { + if ([System.IO.Path]::IsPathRooted($_)) { + $_ + } else { + Join-Path $WorkspacePath $_ + } + } + + $filesWithConflicts = @() + $filesChecked = 0 + + foreach ($filePath in $absolutePaths) { + # Check if file exists (might be deleted) + if (-not (Test-Path $filePath)) { + Write-Verbose " Skipping deleted file: $filePath" + continue + } + + # Skip binary files and directories + if ((Get-Item $filePath) -is [System.IO.DirectoryInfo]) { + continue + } + + $filesChecked++ + + # Get relative path for display + $relativePath = if ($WorkspacePath -and $filePath.StartsWith($WorkspacePath)) { + $filePath.Substring($WorkspacePath.Length).TrimStart([System.IO.Path]::DirectorySeparatorChar, [System.IO.Path]::AltDirectorySeparatorChar) + } else { + $filePath + } + + Write-Host " Checking: $relativePath" -ForegroundColor Gray + + # Search for conflict markers using Select-String + try { + # Git conflict markers are 7 characters followed by a space or end of line + # Regex pattern breakdown: + # ^ - Matches the start of a line + # (<{7}|={7}|>{7}) - Matches exactly 7 consecutive '<', '=', or '>' characters (Git conflict markers) + # (\s|$) - Ensures the marker is followed by whitespace or end of line + $pattern = '^(<{7}|={7}|>{7})(\s|$)' + $matchedLines = Select-String -Path $filePath -Pattern $pattern -AllMatches -ErrorAction Stop + + if ($matchedLines) { + # Collect marker details with line numbers (Select-String provides LineNumber automatically) + $markerDetails = @() + + foreach ($match in $matchedLines) { + $markerDetails += [PSCustomObject]@{ + Marker = $match.Matches[0].Groups[1].Value + Line = $match.LineNumber + } + } + + $filesWithConflicts += [PSCustomObject]@{ + File = $relativePath + MarkerDetails = $markerDetails + } + + Write-Host " ❌ CONFLICT MARKERS FOUND in $relativePath" -ForegroundColor Red + foreach ($detail in $markerDetails) { + Write-Host " Line $($detail.Line): $($detail.Marker)" -ForegroundColor Red + } + } + } + catch { + # Skip files that can't be read (likely binary) + Write-Verbose " Skipping unreadable file: $relativePath" + } + } + + # Output results to GitHub Actions + if ($OutputPath) { + "files-checked=$filesChecked" | Out-File -FilePath $OutputPath -Append -Encoding utf8 + "conflicts-found=$($filesWithConflicts.Count)" | Out-File -FilePath $OutputPath -Append -Encoding utf8 + } + + Write-Host "`nSummary:" -ForegroundColor Cyan + Write-Host " Files checked: $filesChecked" -ForegroundColor Cyan + Write-Host " Files with conflicts: $($filesWithConflicts.Count)" -ForegroundColor Cyan + + # Create GitHub Actions job summary + if ($SummaryPath) { + $summaryContent = @" +# Merge Conflict Marker Check Results + +## Summary +- **Files Checked:** $filesChecked +- **Files with Conflicts:** $($filesWithConflicts.Count) + +"@ + + if ($filesWithConflicts.Count -gt 0) { + Write-Host "`n❌ Merge conflict markers detected in the following files:" -ForegroundColor Red + + $summaryContent += "`n## ❌ Conflicts Detected`n`n" + $summaryContent += "The following files contain merge conflict markers:`n`n" + + foreach ($fileInfo in $filesWithConflicts) { + Write-Host " - $($fileInfo.File)" -ForegroundColor Red + + $summaryContent += "### 📄 ``$($fileInfo.File)```n`n" + $summaryContent += "| Line | Marker |`n" + $summaryContent += "|------|--------|`n" + + foreach ($detail in $fileInfo.MarkerDetails) { + Write-Host " Line $($detail.Line): $($detail.Marker)" -ForegroundColor Red + $summaryContent += "| $($detail.Line) | ``$($detail.Marker)`` |`n" + } + $summaryContent += "`n" + } + + $summaryContent += "`n**Action Required:** Please resolve these conflicts before merging.`n" + Write-Host "`nPlease resolve these conflicts before merging." -ForegroundColor Red + } else { + Write-Host "`n✅ No merge conflict markers found" -ForegroundColor Green + $summaryContent += "`n## ✅ No Conflicts Found`n`nAll checked files are free of merge conflict markers.`n" + } + + $summaryContent | Out-File -FilePath $SummaryPath -Encoding utf8 + } + + # Exit with error if conflicts found + if ($filesWithConflicts.Count -gt 0) { + throw "Merge conflict markers detected in $($filesWithConflicts.Count) file(s)" + } +} diff --git a/tools/clearlyDefined/ClearlyDefined.ps1 b/tools/clearlyDefined/ClearlyDefined.ps1 index 1830c2969e5..c5303b8622b 100644 --- a/tools/clearlyDefined/ClearlyDefined.ps1 +++ b/tools/clearlyDefined/ClearlyDefined.ps1 @@ -21,7 +21,7 @@ if ($ForceModuleReload) { Import-Module -Name "$PSScriptRoot/src/ClearlyDefined" @extraParams -$cgManfest = Get-Content "$PSScriptRoot/../cgmanifest.json" | ConvertFrom-Json +$cgManfest = Get-Content "$PSScriptRoot/../cgmanifest/main/cgmanifest.json" | ConvertFrom-Json $fullCgList = $cgManfest.Registrations.Component | ForEach-Object { [Pscustomobject]@{ diff --git a/tools/clearlyDefined/Find-LastHarvestedVersion.ps1 b/tools/clearlyDefined/Find-LastHarvestedVersion.ps1 new file mode 100644 index 00000000000..a989a3e1fc4 --- /dev/null +++ b/tools/clearlyDefined/Find-LastHarvestedVersion.ps1 @@ -0,0 +1,156 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +<# +.SYNOPSIS + Find the last harvested version of a NuGet package from ClearlyDefined. + +.DESCRIPTION + Searches for the last harvested version of a package by checking versions + backwards from the specified current version. This is useful for reverting + to a known-good harvested version when a newer version hasn't been harvested yet. + +.PARAMETER Name + The NuGet package name to search for. + +.PARAMETER CurrentVersion + The version to start searching backwards from. Version comparison uses semantic versioning. + +.PARAMETER PackageSourceName + The NuGet package source name to use when searching for available versions. + Default is 'findMissingNoticesNugetOrg' if not specified. + +.EXAMPLE + Find-LastHarvestedVersion -Name "Microsoft.Windows.Compatibility" -CurrentVersion "8.0.24" + + # This will return "8.0.22" if that's the last harvested version + +.NOTES + Requires the ClearlyDefined module to be imported: + Import-Module ".\clearlyDefined\src\ClearlyDefined" -Force +#> + +function Find-LastHarvestedVersion { + [CmdletBinding()] + param( + [parameter(Mandatory)] + [string]$Name, + + [parameter(Mandatory)] + [string]$CurrentVersion, + + [string]$PackageSourceName = 'findMissingNoticesNugetOrg' + ) + + try { + Write-Verbose "Finding last harvested version for $Name starting from v$CurrentVersion..." + + # Parse the current version + try { + [System.Management.Automation.SemanticVersion]$currentSemVer = $CurrentVersion + } catch { + [Version]$currentSemVer = $CurrentVersion + } + + # First try the ClearlyDefined search API (more efficient) + try { + Write-Verbose "Searching ClearlyDefined API for versions of $Name (sorted by release date)..." + # Get versions sorted by release date descending (newest first) for efficiency + $versions = Get-ClearlyDefinedPackageVersions -PackageName $Name + + if ($versions -and $versions.Count -gt 0) { + # Results are already sorted by release date newest first + # Filter to versions <= current version + foreach ($versionInfo in $versions) { + try { + $versionObj = [System.Management.Automation.SemanticVersion]$versionInfo.Version + if ($versionObj -le $currentSemVer) { + # Check harvest status + if ($versionInfo.Harvested) { + Write-Verbose "Found harvested version: v$($versionInfo.Version)" + return $versionInfo.Version + } else { + Write-Verbose "v$($versionInfo.Version) - Not harvested, continuing..." + } + } + } catch { + # Skip versions that can't be parsed + } + } + + Write-Verbose "No harvested version found in ClearlyDefined results" + return $null + } + } catch { + Write-Verbose "ClearlyDefined search API failed ($_), falling back to NuGet search..." + } + + # Fallback: Get all available versions from NuGet and check individually + Write-Verbose "Falling back to NuGet source search..." + + # Ensure package source exists + if (!(Get-PackageSource -Name $PackageSourceName -ErrorAction SilentlyContinue)) { + Write-Verbose "Registering package source: $PackageSourceName" + $null = Register-PackageSource -Name $PackageSourceName -Location https://www.nuget.org/api/v2 -ProviderName NuGet + } + + # Get all available versions from NuGet + try { + $allVersions = Find-Package -Name $Name -AllowPrereleaseVersions -source $PackageSourceName -AllVersions -ErrorAction SilentlyContinue | ForEach-Object { + try { + $packageVersion = [System.Management.Automation.SemanticVersion]$_.Version + } catch { + $packageVersion = [Version]$_.Version + } + $_ | Add-Member -Name SemVer -MemberType NoteProperty -Value $packageVersion -PassThru + } | Where-Object { $_.SemVer -le $currentSemVer } | Sort-Object -Property SemVer -Descending | ForEach-Object { $_.Version } + } catch { + Write-Warning "Failed to get versions for $Name : $_" + return $null + } + + if (!$allVersions) { + Write-Verbose "No versions found for $Name" + return $null + } + + # Check each version backwards until we find one that's harvested + foreach ($version in $allVersions) { + $pkg = [PSCustomObject]@{ + type = "nuget" + Name = $Name + PackageVersion = $version + } + + try { + $result = $pkg | Get-ClearlyDefinedData + if ($result -and $result.harvested) { + Write-Verbose "Found harvested version: v$version" + return $version + } else { + Write-Verbose "v$version - Not harvested, continuing..." + } + } catch { + Write-Verbose "Error checking v$version : $_" -Verbose + } + } + + Write-Verbose "No harvested version found for $Name" + return $null + } finally { + Save-ClearlyDefinedCache + } +} + +# If this script is called directly (not sourced), run a test +if ($MyInvocation.InvocationName -eq '.' -or $MyInvocation.Line -like '. "*Find-LastHarvestedVersion*') { + # Script was sourced, just load the function +} else { + # Script was called directly + Write-Host "Testing Find-LastHarvestedVersion function..." + Write-Host "Ensure ClearlyDefined module is loaded first:" + Write-Host ' Import-Module ".\clearlydefined\src\ClearlyDefined" -Force' + Write-Host "" + Write-Host "Example usage:" + Write-Host ' Find-LastHarvestedVersion -Name "Microsoft.Windows.Compatibility" -CurrentVersion "8.0.24"' +} diff --git a/tools/clearlyDefined/src/ClearlyDefined/ClearlyDefined.psm1 b/tools/clearlyDefined/src/ClearlyDefined/ClearlyDefined.psm1 index 4d874402977..88fc1f7cabd 100644 --- a/tools/clearlyDefined/src/ClearlyDefined/ClearlyDefined.psm1 +++ b/tools/clearlyDefined/src/ClearlyDefined/ClearlyDefined.psm1 @@ -2,6 +2,9 @@ # Licensed under the MIT License. # Start the collection (known as harvest) of ClearlyDefined data for a package + +$retryIntervalSec = 90 +$maxRetryCount = 5 function Start-ClearlyDefinedHarvest { [CmdletBinding()] param( @@ -27,7 +30,9 @@ function Start-ClearlyDefinedHarvest { $coordinates = Get-ClearlyDefinedCoordinates @PSBoundParameters $body = @{tool='package';coordinates=$coordinates} | convertto-json Write-Verbose $body -Verbose - (Invoke-WebRequest -Method Post -Uri 'https://api.clearlydefined.io/harvest' -Body $body -ContentType 'application/json' -MaximumRetryCount 5 -RetryIntervalSec 60 -Verbose).Content + Start-job -ScriptBlock { + Invoke-WebRequest -Method Post -Uri 'https://api.clearlydefined.io/harvest' -Body $using:body -ContentType 'application/json' -MaximumRetryCount $using:maxRetryCount -RetryIntervalSec $using:retryIntervalSec + } } } @@ -35,19 +40,30 @@ function ConvertFrom-ClearlyDefinedCoordinates { [CmdletBinding()] param( [parameter(mandatory = $true, ValueFromPipeline = $true)] - [string] + [object] $Coordinates ) Begin {} Process { - $parts = $Coordinates.Split('/') - [PSCustomObject]@{ - type = $parts[0] - provider = $parts[1] - namespace = $parts[2] - name = $parts[3] - revision = $parts[4] + if ($Coordinates -is [string]) { + $parts = $Coordinates.Split('/') + [PSCustomObject]@{ + type = $parts[0] + provider = $parts[1] + namespace = $parts[2] + name = $parts[3] + revision = $parts[4] + } + } else { + # Coordinates is already an object (e.g., from ClearlyDefined API response) + [PSCustomObject]@{ + type = $Coordinates.type + provider = $Coordinates.provider + namespace = $Coordinates.namespace + name = $Coordinates.name + revision = $Coordinates.revision + } } } End {} @@ -74,6 +90,207 @@ Function Get-ClearlyDefinedCoordinates { # Cache of ClearlyDefined data $cdCache = @{} +function Test-ClearlyDefinedCachePersistenceAllowed { + [CmdletBinding()] + param() + + if ($env:TF_BUILD -or $env:ADO_BUILD_ID -or $env:BUILD_BUILDID) { + return $false + } + + if ($env:GITHUB_ACTIONS -or $env:GITHUB_RUN_ID) { + return $false + } + + return $true +} + +function Get-ClearlyDefinedCachePath { + [CmdletBinding()] + param() + + $tempPath = [System.IO.Path]::GetTempPath() + return (Join-Path -Path $tempPath -ChildPath 'clearlydefined-cache.json') +} + +function Save-ClearlyDefinedCache { + [CmdletBinding()] + param() + + if (-not (Test-ClearlyDefinedCachePersistenceAllowed)) { + Write-Verbose 'Skipping cache persistence for CI environment.' + return + } + + if ($cdCache.Count -eq 0) { + Write-Verbose 'No cache entries to persist.' + return + } + + $cachePath = Get-ClearlyDefinedCachePath + $entries = foreach ($key in $cdCache.Keys) { + [PSCustomObject]@{ + coordinates = $key + data = $cdCache[$key] + } + } + + $cachePayload = @{ + savedAtUtc = (Get-Date).ToUniversalTime() + entries = $entries + } | ConvertTo-Json -Depth 20 + + $cachePayload | Set-Content -Path $cachePath -Encoding UTF8 + Write-Verbose "Persisted cache to $cachePath" +} + +function Import-ClearlyDefinedCache { + [CmdletBinding()] + param() + + if (-not (Test-ClearlyDefinedCachePersistenceAllowed)) { + Write-Verbose 'Skipping cache import for CI environment.' + return + } + + $cachePath = Get-ClearlyDefinedCachePath + if (-not (Test-Path -Path $cachePath)) { + Write-Verbose 'No persisted cache found.' + return + } + + try { + $payload = Get-Content -Path $cachePath -Raw | ConvertFrom-Json + } catch { + Write-Verbose "Failed to read cache file: $cachePath" + return + } + + if (-not $payload.entries) { + Write-Verbose 'Cache file did not contain entries.' + return + } + + foreach ($entry in $payload.entries) { + if (-not $entry.coordinates -or -not $entry.data) { + continue + } + + try { + $entry.data.cachedTime = [datetime]$entry.data.cachedTime + } catch { + continue + } + + $cdCache[$entry.coordinates] = $entry.data + } + + Write-Verbose "Imported $($cdCache.Count) cache entries from $cachePath" +} + +# Search for packages in ClearlyDefined +Function Search-ClearlyDefined { + [CmdletBinding()] + param( + [string]$Type = 'nuget', + [string]$Provider = 'nuget', + [string]$Namespace, + [string]$Name, + [string]$Pattern, + [datetime]$ReleasedAfter, + [datetime]$ReleasedBefore, + [ValidateSet('releaseDate', 'name')] + [string]$Sort, + [switch]$SortDesc + ) + + $queryParams = @() + if ($Type) { $queryParams += "type=$([System.Uri]::EscapeDataString($Type))" } + if ($Provider) { $queryParams += "provider=$([System.Uri]::EscapeDataString($Provider))" } + if ($Namespace) { $queryParams += "namespace=$([System.Uri]::EscapeDataString($Namespace))" } + if ($Name) { $queryParams += "name=$([System.Uri]::EscapeDataString($Name))" } + if ($Pattern) { $queryParams += "pattern=$([System.Uri]::EscapeDataString($Pattern))" } + if ($ReleasedAfter) { $queryParams += "releasedAfter=$($ReleasedAfter.ToString('o'))" } + if ($ReleasedBefore) { $queryParams += "releasedBefore=$($ReleasedBefore.ToString('o'))" } + if ($Sort) { $queryParams += "sort=$([System.Uri]::EscapeDataString($Sort))" } + if ($SortDesc) { $queryParams += "sortDesc=true" } + + $searchUri = "https://api.clearlydefined.io/definitions?" + ($queryParams -join '&') + Write-Verbose "Searching ClearlyDefined: $searchUri" + + try { + $results = Invoke-RestMethod -Uri $searchUri -MaximumRetryCount $maxRetryCount -RetryIntervalSec $retryIntervalSec + return $results + } catch { + if ($retryIntervalSec -lt 300) { + $retryIntervalSec++ + } + + Write-Warning "Failed to search ClearlyDefined: $_" + return $null + } +} + +# Get available versions for a NuGet package with harvest status +Function Get-ClearlyDefinedPackageVersions { + [CmdletBinding()] + param( + [parameter(mandatory = $true)] + [string] + $PackageName, + + [validateset('nuget')] + [string] + $PackageType = 'nuget' + ) + + # Search for all definitions of this package, sorted by release date (newest first) + Write-Verbose "Fetching versions of $PackageName from ClearlyDefined..." + + $results = Search-ClearlyDefined -Type $PackageType -Provider nuget -Name $PackageName -Sort releaseDate -SortDesc + + if (!$results) { + Write-Verbose "No results found for $PackageName" + return @() + } + + # Convert results to version info objects + $versions = @() + + # API returns results in different formats depending on the query + $dataArray = $null + if ($results.data) { + $dataArray = $results.data + } elseif ($results -is [array]) { + $dataArray = $results + } elseif ($results.PSObject.Properties.Count -gt 0) { + # If it's an object with properties, try to extract the actual results + foreach ($prop in $results.PSObject.Properties) { + if ($prop.Value -is [object] -and $prop.Value.revision) { + $dataArray += $prop.Value + } + } + } + + if ($dataArray) { + foreach ($item in $dataArray) { + if ($item.revision) { + $harvested = if ($item.licensed -and $item.licensed.declared) { $true } else { $false } + + $versions += [PSCustomObject]@{ + Name = $item.name + Version = $item.revision + Harvested = $harvested + Licensed = $item.licensed.declared + } + } + } + } + + # Results are already sorted by API, no need to re-sort + return $versions +} + # Get the ClearlyDefined data for a package Function Get-ClearlyDefinedData { [CmdletBinding()] @@ -96,8 +313,9 @@ Function Get-ClearlyDefinedData { ) Begin { - $cacheMinutes = 60 - $cacheCutoff = (get-date).AddMinutes(-$cacheMinutes) + # Different TTLs for different cache types + $harvestedCacheMinutes = 60 # Cache positive results for 60 minutes + $nonHarvestedCacheMinutes = 30 # Cache negative results for 30 minutes (less aggressive) $coordinateList = @() } @@ -111,19 +329,55 @@ Function Get-ClearlyDefinedData { foreach($coordinates in $coordinateList) { Write-Progress -Activity "Getting ClearlyDefined data" -Status "Getting data for $coordinates" -PercentComplete (($completed / $total) * 100) $containsKey = $cdCache.ContainsKey($coordinates) - if ($containsKey -and $cdCache[$coordinates].cachedTime -gt $cacheCutoff) { - Write-Verbose "Returning cached data for $coordinates" - Write-Output $cdCache[$coordinates] - continue + + if ($containsKey) { + $cached = $cdCache[$coordinates] + # Check if cache entry is still valid based on its type + $cacheCutoff = if ($cached.harvestedResult) { + (get-date).AddMinutes(-$harvestedCacheMinutes) + } else { + (get-date).AddMinutes(-$nonHarvestedCacheMinutes) + } + + if ($cached.cachedTime -gt $cacheCutoff) { + Write-Progress -Activity "Getting ClearlyDefined data" -Status "Getting data for $coordinates - cache hit" -PercentComplete (($completed / $total) * 100) + Write-Verbose "Returning cached data for $coordinates (harvested: $($cached.harvestedResult))" + Write-Output $cached + $completed++ + continue + } } - Invoke-RestMethod -Uri "https://api.clearlydefined.io/definitions/$coordinates" -MaximumRetryCount 5 -RetryIntervalSec 60 | ForEach-Object { - [bool] $harvested = if ($_.licensed.declared) { $true } else { $false } - Add-Member -NotePropertyName cachedTime -NotePropertyValue (get-date) -InputObject $_ -PassThru | Add-Member -NotePropertyName harvested -NotePropertyValue $harvested -PassThru - if ($_.harvested) { - Write-Verbose "Caching data for $coordinates" - $cdCache[$coordinates] = $_ + Write-Progress -Activity "Getting ClearlyDefined data" -Status "Getting data for $coordinates - cache miss" -PercentComplete (($completed / $total) * 100) + + try { + Invoke-RestMethod -Uri "https://api.clearlydefined.io/definitions/$coordinates" -MaximumRetryCount $maxRetryCount -RetryIntervalSec $retryIntervalSec | ForEach-Object { + [bool] $harvested = if ($_.licensed.declared) { $true } else { $false } + # Always cache, with harvestedResult property to distinguish for TTL purposes + Add-Member -NotePropertyName cachedTime -NotePropertyValue (get-date) -InputObject $_ -PassThru | + Add-Member -NotePropertyName harvested -NotePropertyValue $harvested -PassThru | + Add-Member -NotePropertyName harvestedResult -NotePropertyValue $harvested -PassThru | + ForEach-Object { + Write-Verbose "Caching data for $coordinates (harvested: $($_.harvested))" + $cdCache[$coordinates] = $_ + Write-Output $_ + } + } + } catch { + if ($retryIntervalSec -lt 300) { + $retryIntervalSec++ + } + + Write-Warning "Failed to get ClearlyDefined data for $coordinates : $_" + # Return a minimal object indicating failure/not harvested so the pipeline continues + $failedResult = [PSCustomObject]@{ + coordinates = $coordinates + harvested = $false + harvestedResult = $false + cachedTime = (get-date) + licensed = @{ declared = $null } } + Write-Output $failedResult } $completed++ } @@ -134,4 +388,10 @@ Export-ModuleMember -Function @( 'Start-ClearlyDefinedHarvest' 'Get-ClearlyDefinedData' 'ConvertFrom-ClearlyDefinedCoordinates' + 'Search-ClearlyDefined' + 'Get-ClearlyDefinedPackageVersions' + 'Save-ClearlyDefinedCache' + 'Import-ClearlyDefinedCache' + 'Test-ClearlyDefinedCachePersistenceAllowed' + 'Get-ClearlyDefinedCachePath' ) diff --git a/tools/download.sh b/tools/download.sh index 6a6c6436b4b..f1e8c42cdc3 100644 --- a/tools/download.sh +++ b/tools/download.sh @@ -1 +1,3 @@ -bash <(curl -s https://raw.githubusercontent.com/PowerShell/PowerShell/master/tools/install-powershell.sh) +# Pin to specific commit for security (OpenSSF Scorecard requirement) +# Pinned commit: 26bb188c8 - "Improve ValidateLength error message consistency and refactor validation tests" (2025-10-12) +bash <(curl -s https://raw.githubusercontent.com/PowerShell/PowerShell/26bb188c8be0cda6cb548ce1a12840ebf67e1331/tools/install-powershell.sh) diff --git a/tools/findMissingNotices.ps1 b/tools/findMissingNotices.ps1 index 42722701d97..5ed931c5caa 100644 --- a/tools/findMissingNotices.ps1 +++ b/tools/findMissingNotices.ps1 @@ -7,12 +7,53 @@ param( [switch] $Fix, - [switch] $IsStable + [switch] $IsStable, + [switch] $ForceHarvestedOnly ) Import-Module dotnet.project.assets Import-Module "$PSScriptRoot\..\.github\workflows\GHWorkflowHelper" -Force . "$PSScriptRoot\..\tools\buildCommon\startNativeExecution.ps1" +. "$PSScriptRoot\clearlyDefined\Find-LastHarvestedVersion.ps1" + +$targetsConfigPath = Join-Path -Path $PSScriptRoot -ChildPath 'findMissingNotices.targets.json' +if (-not (Test-Path -LiteralPath $targetsConfigPath)) { + throw "Missing target framework config file '$targetsConfigPath'. Add '/tools/findMissingNotices.targets.json' with 'dotnetTargetName' and 'windowsTargetNames' entries." +} + +try { + $targetsConfig = Get-Content -LiteralPath $targetsConfigPath -Raw -ErrorAction Stop | ConvertFrom-Json -AsHashtable -ErrorAction Stop +} catch { + throw "Failed to load target framework config from '$targetsConfigPath'. Ensure the file contains valid JSON. Error: $($_.Exception.Message)" +} + +if ($targetsConfig -isnot [hashtable]) { + throw "Invalid target framework config '$targetsConfigPath': expected a JSON object with 'dotnetTargetName' and 'windowsTargetNames'." +} + +if (-not $targetsConfig.ContainsKey('dotnetTargetName') -or [string]::IsNullOrWhiteSpace($targetsConfig['dotnetTargetName'])) { + throw "Invalid target framework config '$targetsConfigPath': 'dotnetTargetName' must be a non-empty string." +} + +if (-not $targetsConfig.ContainsKey('windowsTargetNames')) { + throw "Invalid target framework config '$targetsConfigPath': 'windowsTargetNames' must be present and must be an array." +} + +if ($null -eq $targetsConfig['windowsTargetNames'] -or $targetsConfig['windowsTargetNames'] -isnot [array]) { + throw "Invalid target framework config '$targetsConfigPath': 'windowsTargetNames' must be an array (empty array is allowed)." +} + +$script:dotnetTargetName = [string]$targetsConfig['dotnetTargetName'] +$script:windowsTargetNames = @() +foreach ($windowsTargetName in $targetsConfig['windowsTargetNames']) { + if ($windowsTargetName -isnot [string] -or [string]::IsNullOrWhiteSpace($windowsTargetName)) { + throw "Invalid target framework config '$targetsConfigPath': every entry in 'windowsTargetNames' must be a non-empty string." + } + + $script:windowsTargetNames += $windowsTargetName +} + +# Empty windowsTargetNames is valid and means "use base target fallback only". $packageSourceName = 'findMissingNoticesNugetOrg' if (!(Get-PackageSource -Name $packageSourceName -ErrorAction SilentlyContinue)) { @@ -20,7 +61,7 @@ if (!(Get-PackageSource -Name $packageSourceName -ErrorAction SilentlyContinue)) } $existingRegistrationTable = @{} -$cgManifestPath = (Resolve-Path -Path $PSScriptRoot\..\tools\cgmanifest.json).ProviderPath +$cgManifestPath = (Resolve-Path -Path $PSScriptRoot\cgmanifest\main\cgmanifest.json).ProviderPath $existingRegistrationsJson = Get-Content $cgManifestPath | ConvertFrom-Json -AsHashtable $existingRegistrationsJson.Registrations | ForEach-Object { $registration = [Registration]$_ @@ -193,8 +234,7 @@ function Get-CGRegistrations { $registrationChanged = $false - $dotnetTargetName = 'net10.0' - $dotnetTargetNameWin7 = 'net10.0-windows8.0' + $baseTargetName = $script:dotnetTargetName $unixProjectName = 'powershell-unix' $windowsProjectName = 'powershell-win-core' $actualRuntime = $Runtime @@ -202,35 +242,30 @@ function Get-CGRegistrations { switch -regex ($Runtime) { "alpine-.*" { $folder = $unixProjectName - $target = "$dotnetTargetName|$Runtime" - $neutralTarget = "$dotnetTargetName" + $target = "$baseTargetName|$Runtime" + $neutralTarget = "$baseTargetName" } "linux-.*" { $folder = $unixProjectName - $target = "$dotnetTargetName|$Runtime" - $neutralTarget = "$dotnetTargetName" + $target = "$baseTargetName|$Runtime" + $neutralTarget = "$baseTargetName" } "osx-.*" { $folder = $unixProjectName - $target = "$dotnetTargetName|$Runtime" - $neutralTarget = "$dotnetTargetName" - } - "win-x*" { - $sdkToUse = $winDesktopSdk - $folder = $windowsProjectName - $target = "$dotnetTargetNameWin7|$Runtime" - $neutralTarget = "$dotnetTargetNameWin7" + $target = "$baseTargetName|$Runtime" + $neutralTarget = "$baseTargetName" } "win-.*" { + $sdkToUse = $winDesktopSdk $folder = $windowsProjectName - $target = "$dotnetTargetNameWin7|$Runtime" - $neutralTarget = "$dotnetTargetNameWin7" + $target = "$baseTargetName|$actualRuntime" + $neutralTarget = "$baseTargetName" } "modules" { $folder = "modules" $actualRuntime = 'linux-x64' - $target = "$dotnetTargetName|$actualRuntime" - $neutralTarget = "$dotnetTargetName" + $target = "$baseTargetName|$actualRuntime" + $neutralTarget = "$baseTargetName" } Default { throw "Invalid runtime name: $Runtime" @@ -245,6 +280,50 @@ function Get-CGRegistrations { dotnet restore --runtime $actualRuntime "/property:SDKToUse=$sdkToUse" } $null = New-PADrive -Path $PSScriptRoot\..\src\$folder\obj\project.assets.json -Name $folder + + if ($Runtime -like "win-*") { + # Windows target selection is optional and ordered: + # 1. Try full Windows TFMs from config in order. + # 2. Fall back to the base non-Windows TFM if present. + try { + $availableTargets = @(Get-ChildItem -Path "${folder}:/targets" -ErrorAction Stop | Select-Object -ExpandProperty Name) + } catch { + throw "Unable to enumerate available targets for runtime '$Runtime' in '$folder'. Ensure dotnet restore succeeded and project.assets.json contains target data. Error: $($_.Exception.Message)" + } + + $selectedTargetName = $null + + foreach ($windowsTargetName in $script:windowsTargetNames) { + if ($windowsTargetName -in $availableTargets) { + $selectedTargetName = $windowsTargetName + break + } + } + + if (-not $selectedTargetName -and $baseTargetName -in $availableTargets) { + Write-Verbose "No configured windows target matched for '$Runtime'. Falling back to base target '$baseTargetName'." -Verbose + $selectedTargetName = $baseTargetName + } + + if (-not $selectedTargetName) { + Write-Verbose "Available targets for '$folder': $($availableTargets -join ', ')" -Verbose + if ($script:windowsTargetNames.Count -eq 0) { + throw "Unable to find a target for '$Runtime'. Tried fallback base target '$baseTargetName' (no windowsTargetNames configured). Ensure project.assets.json contains this target or update dotnetTargetName in '$targetsConfigPath'." + } + + throw "Unable to find a target for '$Runtime'. Tried configured windowsTargetNames '$($script:windowsTargetNames -join "', '")' and fallback base target '$baseTargetName'. Update '$targetsConfigPath' with a valid windows target from the available list." + } + + $target = "$selectedTargetName|$actualRuntime" + $neutralTarget = $selectedTargetName + } + + # Defensive check: non-Windows paths set targets in the switch block, + # Windows path may override them after inspecting available assets targets. + if (-not $target -or -not $neutralTarget) { + throw "Unable to determine restore targets for runtime '$Runtime'." + } + try { $targets = Get-ChildItem -Path "${folder}:/targets/$target" -ErrorAction Stop | Where-Object { $_.Type -eq 'package' } | select-object -ExpandProperty name $targets += Get-ChildItem -Path "${folder}:/targets/$neutralTarget" -ErrorAction Stop | Where-Object { $_.Type -eq 'project' } | select-object -ExpandProperty name @@ -335,8 +414,91 @@ if ($IsStable) { } $count = $newRegistrations.Count +$registrationsToSave = $newRegistrations +$tpnRegistrationsToSave = $null + +# If -ForceHarvestedOnly is specified with -Fix, only include harvested packages +# and revert non-harvested packages to their previous versions +if ($Fix -and $ForceHarvestedOnly) { + Write-Verbose "Checking harvest status and filtering to harvested packages with reversion..." -Verbose + + # Import ClearlyDefined module to check harvest status + Import-Module -Name "$PSScriptRoot/clearlyDefined/src/ClearlyDefined" -Force + + # Import cache from previous runs to speed up lookups + Import-ClearlyDefinedCache + + # Get harvest data for all registrations + $fullCgList = $newRegistrations | + ForEach-Object { + [PSCustomObject]@{ + type = $_.Component.Type + Name = $_.Component.Nuget.Name + PackageVersion = $_.Component.Nuget.Version + } + } + + $fullList = $fullCgList | Get-ClearlyDefinedData + + # Build a lookup table of harvest status by package name + version + $harvestStatus = @{} + foreach ($item in $fullList) { + $key = "$($item.Name)|$($item.PackageVersion)" + $harvestStatus[$key] = $item.harvested + } + + # Build a lookup table of old versions from existing manifest + $oldVersions = @{} + foreach ($registration in $existingRegistrationsJson.Registrations) { + $name = $registration.Component.Nuget.Name + if (!$oldVersions.ContainsKey($name)) { + $oldVersions[$name] = $registration + } + } + + # Process each new registration: keep harvested, revert non-harvested + $tpnRegistrationsToSave = @() + $harvestedCount = 0 + $revertedCount = 0 + + foreach ($reg in $newRegistrations) { + $name = $reg.Component.Nuget.Name + $version = $reg.Component.Nuget.Version + $key = "$name|$version" + + if ($harvestStatus.ContainsKey($key) -and $harvestStatus[$key]) { + # Package is harvested, include it + $tpnRegistrationsToSave += $reg + $harvestedCount++ + } else { + # Package not harvested, find last harvested version + $lastHarvestedVersion = Find-LastHarvestedVersion -Name $name -CurrentVersion $version + + # Use last harvested version if found, otherwise use old version as fallback + if ($lastHarvestedVersion) { + if ($lastHarvestedVersion -ne $version) { + $revertedReg = New-NugetComponent -Name $name -Version $lastHarvestedVersion -DevelopmentDependency:$reg.DevelopmentDependency + $tpnRegistrationsToSave += $revertedReg + $revertedCount++ + Write-Verbose "Reverted $name from v$version to last harvested v$lastHarvestedVersion" -Verbose + } else { + $tpnRegistrationsToSave += $reg + } + } elseif ($oldVersions.ContainsKey($name)) { + $tpnRegistrationsToSave += $oldVersions[$name] + $revertedCount++ + Write-Verbose "Reverted $name to previous version (no harvested version found)" -Verbose + } else { + Write-Warning "$name v$version not harvested and no previous version found. Excluding from manifest." + } + } + } + + Write-Verbose "Completed filtering for TPN: $harvestedCount harvested + $revertedCount reverted = $($tpnRegistrationsToSave.Count) total" -Verbose +} + $newJson = @{ - Registrations = $newRegistrations + Registrations = $registrationsToSave '$schema' = "https://json.schemastore.org/component-detection-manifest.json" } | ConvertTo-Json -depth 99 @@ -345,6 +507,149 @@ if ($Fix -and $registrationChanged) { Set-GWVariable -Name CGMANIFEST_PATH -Value $cgManifestPath } +# If -ForceHarvestedOnly was used, write the TPN manifest with filtered registrations +if ($Fix -and $ForceHarvestedOnly -and $tpnRegistrationsToSave.Count -gt 0) { + $tpnManifestDir = Join-Path -Path $PSScriptRoot -ChildPath "cgmanifest\tpn" + New-Item -ItemType Directory -Path $tpnManifestDir -Force | Out-Null + $tpnManifestPath = Join-Path -Path $tpnManifestDir -ChildPath "cgmanifest.json" + + $tpnManifest = @{ + Registrations = @($tpnRegistrationsToSave) + '$schema' = "https://json.schemastore.org/component-detection-manifest.json" + } + + $tpnJson = $tpnManifest | ConvertTo-Json -depth 99 + $tpnJson | Set-Content $tpnManifestPath -Encoding utf8NoBOM + Write-Verbose "TPN manifest created/updated with $($tpnRegistrationsToSave.Count) registrations (filtered for harvested packages)" -Verbose +} + +# Skip legacy TPN update when -ForceHarvestedOnly already produced a filtered manifest +if ($Fix -and $registrationChanged -and -not $ForceHarvestedOnly) { + # Import ClearlyDefined module to check harvest status + Write-Verbose "Checking harvest status for newly added packages..." -Verbose + Import-Module -Name "$PSScriptRoot/clearlyDefined/src/ClearlyDefined" -Force + + # Get harvest data for all registrations + $fullCgList = $newRegistrations | + ForEach-Object { + [PSCustomObject]@{ + type = $_.Component.Type + Name = $_.Component.Nuget.Name + PackageVersion = $_.Component.Nuget.Version + } + } + + $fullList = $fullCgList | Get-ClearlyDefinedData + $needHarvest = $fullList | Where-Object { !$_.harvested } + + if ($needHarvest.Count -gt 0) { + Write-Verbose "Found $($needHarvest.Count) packages that need harvesting. Starting harvest..." -Verbose + $needHarvest | Select-Object -ExpandProperty coordinates | ConvertFrom-ClearlyDefinedCoordinates | Start-ClearlyDefinedHarvest + } else { + Write-Verbose "All packages are already harvested." -Verbose + } + + # After manifest update and harvest, update TPN manifest with individual package status + Write-Verbose "Updating TPN manifest with individual package harvest status..." -Verbose + $tpnManifestDir = Join-Path -Path $PSScriptRoot -ChildPath "cgmanifest\tpn" + $tpnManifestPath = Join-Path -Path $tpnManifestDir -ChildPath "cgmanifest.json" + + # Load current TPN manifest to get previous versions + $currentTpnManifest = @() + if (Test-Path $tpnManifestPath) { + $currentTpnJson = Get-Content $tpnManifestPath | ConvertFrom-Json -AsHashtable + $currentTpnManifest = $currentTpnJson.Registrations + } + + # Build a lookup table of old versions + $oldVersions = @{} + foreach ($registration in $currentTpnManifest) { + $name = $registration.Component.Nuget.Name + if (!$oldVersions.ContainsKey($name)) { + $oldVersions[$name] = $registration + } + } + + # Note: Do not recheck harvest status here. Harvesting is an async process that takes a significant amount of time. + # Use the harvest data from the initial check. Newly triggered harvests will be captured + # on the next run of this script after harvesting completes. + $finalHarvestData = $fullList + + # Update packages individually based on harvest status + $tpnRegistrations = @() + $harvestedCount = 0 + $restoredCount = 0 + + foreach ($item in $finalHarvestData) { + $matchingNewRegistration = $newRegistrations | Where-Object { + $_.Component.Nuget.Name -eq $item.Name -and + $_.Component.Nuget.Version -eq $item.PackageVersion + } + + if ($matchingNewRegistration) { + if ($item.harvested) { + # Use new harvested version + $tpnRegistrations += $matchingNewRegistration + $harvestedCount++ + } else { + # Package not harvested - find the last harvested version from ClearlyDefined API + Write-Verbose "Finding last harvested version for $($item.Name)..." -Verbose + + $lastHarvestedVersion = $null + try { + # Search through all versions of this package to find the last harvested one + # Create a list of versions we know about from all runtimes + $packageVersionsToCheck = $newRegistrations | Where-Object { + $_.Component.Nuget.Name -eq $item.Name + } | ForEach-Object { $_.Component.Nuget.Version } | Sort-Object -Unique -Descending + + foreach ($versionToCheck in $packageVersionsToCheck) { + $versionCheckList = [PSCustomObject]@{ + type = "nuget" + Name = $item.Name + PackageVersion = $versionToCheck + } + + $versionStatus = $versionCheckList | Get-ClearlyDefinedData + if ($versionStatus -and $versionStatus.harvested) { + $lastHarvestedVersion = $versionToCheck + break # Found the most recent harvested version + } + } + } catch { + Write-Verbose "Error checking harvested versions for $($item.Name): $_" -Verbose + } + + # Use last harvested version if found, otherwise use old version as fallback + if ($lastHarvestedVersion) { + $revertedReg = New-NugetComponent -Name $item.Name -Version $lastHarvestedVersion -DevelopmentDependency:$matchingNewRegistration.DevelopmentDependency + $tpnRegistrations += $revertedReg + $restoredCount++ + Write-Verbose "Reverted $($item.Name) from v$($item.PackageVersion) to last harvested v$lastHarvestedVersion" -Verbose + } elseif ($oldVersions.ContainsKey($item.Name)) { + $tpnRegistrations += $oldVersions[$item.Name] + $restoredCount++ + Write-Verbose "Reverted $($item.Name) to previous version in TPN (no harvested version found)" -Verbose + } else { + Write-Warning "$($item.Name) v$($item.PackageVersion) not harvested and no harvested version found. Excluding from TPN manifest." + } + } + } + } + + # Save updated TPN manifest + if ($tpnRegistrations.Count -gt 0) { + $tpnManifest = @{ + Registrations = @($tpnRegistrations) + '$schema' = "https://json.schemastore.org/component-detection-manifest.json" + } + + $tpnJson = $tpnManifest | ConvertTo-Json -depth 99 + $tpnJson | Set-Content $tpnManifestPath -Encoding utf8NoBOM + Write-Verbose "TPN manifest updated: $harvestedCount new harvested + $restoredCount reverted to last harvested versions" -Verbose + } +} + if (!$Fix -and $registrationChanged) { $temp = Get-GWTempPath diff --git a/tools/findMissingNotices.targets.json b/tools/findMissingNotices.targets.json new file mode 100644 index 00000000000..a347ee4c3be --- /dev/null +++ b/tools/findMissingNotices.targets.json @@ -0,0 +1,5 @@ +{ + "dotnetTargetName": "net11.0", + "windowsTargetNames": [ + ] +} diff --git a/tools/install-powershell.sh b/tools/install-powershell.sh index 128f5664483..91425c183a8 100755 --- a/tools/install-powershell.sh +++ b/tools/install-powershell.sh @@ -26,7 +26,9 @@ install(){ #gitrepo paths are overrideable to run from your own fork or branch for testing or private distribution local VERSION="1.2.0" - local gitreposubpath="PowerShell/PowerShell/master" + # Pin to specific commit for security (OpenSSF Scorecard requirement) + # Pinned commit: 26bb188c8 - "Improve ValidateLength error message consistency and refactor validation tests" (2025-10-12) + local gitreposubpath="PowerShell/PowerShell/26bb188c8be0cda6cb548ce1a12840ebf67e1331" local gitreposcriptroot="https://raw.githubusercontent.com/$gitreposubpath/tools" local gitscriptname="install-powershell.psh" @@ -125,7 +127,7 @@ install(){ if [[ $osname = *SUSE* ]]; then DistroBasedOn='suse' REV=$(source /etc/os-release; echo $VERSION_ID) - fi + fi OS=$(lowercase $OS) DistroBasedOn=$(lowercase $DistroBasedOn) fi diff --git a/tools/metadata.json b/tools/metadata.json index bbe6977d33e..b2c8b561502 100644 --- a/tools/metadata.json +++ b/tools/metadata.json @@ -1,10 +1,10 @@ { - "StableReleaseTag": "v7.5.2", - "PreviewReleaseTag": "v7.6.0-preview.4", + "StableReleaseTag": "v7.6.1", + "PreviewReleaseTag": "v7.7.0-preview.1", "ServicingReleaseTag": "v7.0.13", - "ReleaseTag": "v7.5.2", - "LTSReleaseTag" : ["v7.4.11"], - "NextReleaseTag": "v7.6.0-preview.5", - "LTSRelease": { "Latest": false, "Package": false }, - "StableRelease": { "Latest": false, "Package": false } + "ReleaseTag": "v7.6.1", + "LTSReleaseTag": ["v7.4.15", "v7.6.1"], + "NextReleaseTag": "v7.7.0-preview.2", + "LTSRelease": { "PublishToChannels": false, "Package": false }, + "StableRelease": { "PublishToChannels": false, "Package": false } } diff --git a/tools/packages.microsoft.com/mapping.json b/tools/packages.microsoft.com/mapping.json index 682c96d9110..7cb212ffee3 100644 --- a/tools/packages.microsoft.com/mapping.json +++ b/tools/packages.microsoft.com/mapping.json @@ -22,36 +22,11 @@ "PackageFormat": "PACKAGE_NAME-POWERSHELL_RELEASE-1.rh.x86_64.rpm" }, { - "url": "cbl-mariner-2.0-prod-Microsoft-aarch64", - "distribution": [ - "bionic" - ], - "PackageFormat": "PACKAGE_NAME-POWERSHELL_RELEASE-1.cm.aarch64.rpm", - "channel": "stable" - }, - { - "url": "cbl-mariner-2.0-prod-Microsoft-x86_64", - "distribution": [ - "bionic" - ], - "PackageFormat": "PACKAGE_NAME-POWERSHELL_RELEASE-1.cm.x86_64.rpm", - "channel": "stable" - }, - { - "url": "cbl-mariner-2.0-preview-Microsoft-aarch64", - "distribution": [ - "bionic" - ], - "PackageFormat": "PACKAGE_NAME-POWERSHELL_RELEASE-1.cm.aarch64.rpm", - "channel": "preview" - }, - { - "url": "cbl-mariner-2.0-preview-Microsoft-x86_64", + "url": "microsoft-rhel10-prod", "distribution": [ - "bionic" + "stable" ], - "PackageFormat": "PACKAGE_NAME-POWERSHELL_RELEASE-1.cm.x86_64.rpm", - "channel": "preview" + "PackageFormat": "PACKAGE_NAME-POWERSHELL_RELEASE-1.rh.x86_64.rpm" }, { "url": "azurelinux-3.0-prod-ms-oss-aarch64", @@ -99,6 +74,27 @@ ], "PackageFormat": "PACKAGE_NAME_POWERSHELL_RELEASE-1.deb_amd64.deb" }, + { + "url": "microsoft-debian-bullseye-prod", + "distribution": [ + "bullseye" + ], + "PackageFormat": "PACKAGE_NAME_POWERSHELL_RELEASE-1.deb_amd64.deb" + }, + { + "url": "microsoft-debian-bookworm-prod", + "distribution": [ + "bookworm" + ], + "PackageFormat": "PACKAGE_NAME_POWERSHELL_RELEASE-1.deb_amd64.deb" + }, + { + "url": "microsoft-debian-trixie-prod", + "distribution": [ + "trixie" + ], + "PackageFormat": "PACKAGE_NAME_POWERSHELL_RELEASE-1.deb_amd64.deb" + }, { "url": "microsoft-ubuntu-bionic-prod", "distribution": [ @@ -133,20 +129,6 @@ "xenial" ], "PackageFormat": "PACKAGE_NAME_POWERSHELL_RELEASE-1.deb_amd64.deb" - }, - { - "url": "microsoft-debian-bullseye-prod", - "distribution": [ - "bullseye" - ], - "PackageFormat": "PACKAGE_NAME_POWERSHELL_RELEASE-1.deb_amd64.deb" - }, - { - "url": "microsoft-debian-bookworm-prod", - "distribution": [ - "bookworm" - ], - "PackageFormat": "PACKAGE_NAME_POWERSHELL_RELEASE-1.deb_amd64.deb" } ] } diff --git a/tools/packaging/boms/linux.json b/tools/packaging/boms/linux.json index 9f5f886a4ca..db29e47a290 100644 --- a/tools/packaging/boms/linux.json +++ b/tools/packaging/boms/linux.json @@ -2442,5 +2442,15 @@ { "Pattern": "System.Management.Automation.dll", "FileType": "Product" + }, + { + "Pattern": "pwsh.profile.dsc.resource.json", + "FileType": "Product", + "Architecture": null + }, + { + "Pattern": "pwsh.profile.resource.ps1", + "FileType": "Product", + "Architecture": null } ] diff --git a/tools/packaging/boms/mac.json b/tools/packaging/boms/mac.json index a4b5bc2bffb..af4fcb0c967 100644 --- a/tools/packaging/boms/mac.json +++ b/tools/packaging/boms/mac.json @@ -2218,5 +2218,15 @@ { "Pattern": "System.Management.Automation.dll", "FileType": "Product" + }, + { + "Pattern": "pwsh.profile.dsc.resource.json", + "FileType": "Product", + "Architecture": null + }, + { + "Pattern": "pwsh.profile.resource.ps1", + "FileType": "Product", + "Architecture": null } ] diff --git a/tools/packaging/boms/windows.json b/tools/packaging/boms/windows.json index 6edb2da4598..8d8154e7445 100644 --- a/tools/packaging/boms/windows.json +++ b/tools/packaging/boms/windows.json @@ -1,3531 +1,4624 @@ [ { "Pattern": "_manifest\\spdx_2.2\\bsi.json", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "_manifest\\spdx_2.2\\manifest.cat", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Accessibility.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "build.manifest", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "build.manifest.sig", + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "clretwrc.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "clrgc.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "clrgcexp.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "clrjit.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "coreclr.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "createdump.exe", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "cs/Microsoft.CodeAnalysis.CSharp.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "cs/Microsoft.CodeAnalysis.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "cs/Microsoft.VisualBasic.Forms.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "cs/PresentationCore.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "cs/PresentationFramework.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "cs/PresentationUI.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "cs/ReachFramework.resources.dll", - "FileType": "NonProduct" - }, - { - "Pattern": "cs/System.Private.ServiceModel.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "cs/System.Web.Services.Description.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "cs/System.Windows.Controls.Ribbon.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "cs/System.Windows.Forms.Design.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "cs/System.Windows.Forms.Primitives.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "cs/System.Windows.Forms.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "cs/System.Windows.Input.Manipulations.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "cs/System.Xaml.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "cs/UIAutomationClient.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "cs/UIAutomationClientSideProviders.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "cs/UIAutomationProvider.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "cs/UIAutomationTypes.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "cs/WindowsBase.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "cs/WindowsFormsIntegration.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "cs\\System.ServiceModel.Http.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "cs\\System.ServiceModel.NetFramingBase.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "cs\\System.ServiceModel.NetTcp.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "cs\\System.ServiceModel.Primitives.resources.dll", + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "D3DCompiler_47_cor3.dll", "FileType": "NonProduct", - "Architecture": ["x64, x86"] + "Architecture": [ + "x64, x86" + ] }, { "Pattern": "de/Microsoft.CodeAnalysis.CSharp.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "de/Microsoft.CodeAnalysis.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "de/Microsoft.VisualBasic.Forms.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "de/PresentationCore.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "de/PresentationFramework.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "de/PresentationUI.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "de/ReachFramework.resources.dll", - "FileType": "NonProduct" - }, - { - "Pattern": "de/System.Private.ServiceModel.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "de/System.Web.Services.Description.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "de/System.Windows.Controls.Ribbon.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "de/System.Windows.Forms.Design.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "de/System.Windows.Forms.Primitives.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "de/System.Windows.Forms.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "de/System.Windows.Input.Manipulations.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "de/System.Xaml.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "de/UIAutomationClient.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "de/UIAutomationClientSideProviders.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "de/UIAutomationProvider.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "de/UIAutomationTypes.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "de/WindowsBase.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "de/WindowsFormsIntegration.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "de\\System.ServiceModel.Http.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "de\\System.ServiceModel.NetFramingBase.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "de\\System.ServiceModel.NetTcp.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "de\\System.ServiceModel.Primitives.resources.dll", + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "DirectWriteForwarder.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "en-US/default.help.txt", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "es/Microsoft.CodeAnalysis.CSharp.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "es/Microsoft.CodeAnalysis.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "es/Microsoft.VisualBasic.Forms.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "es/PresentationCore.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "es/PresentationFramework.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "es/PresentationUI.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "es/ReachFramework.resources.dll", - "FileType": "NonProduct" - }, - { - "Pattern": "es/System.Private.ServiceModel.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "es/System.Web.Services.Description.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "es/System.Windows.Controls.Ribbon.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "es/System.Windows.Forms.Design.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "es/System.Windows.Forms.Primitives.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "es/System.Windows.Forms.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "es/System.Windows.Input.Manipulations.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "es/System.Xaml.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "es/UIAutomationClient.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "es/UIAutomationClientSideProviders.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "es/UIAutomationProvider.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "es/UIAutomationTypes.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "es/WindowsBase.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "es/WindowsFormsIntegration.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "es\\System.ServiceModel.Http.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "es\\System.ServiceModel.NetFramingBase.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "es\\System.ServiceModel.NetTcp.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "es\\System.ServiceModel.Primitives.resources.dll", + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "fr/Microsoft.CodeAnalysis.CSharp.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "fr/Microsoft.CodeAnalysis.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "fr/Microsoft.VisualBasic.Forms.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "fr/PresentationCore.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "fr/PresentationFramework.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "fr/PresentationUI.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "fr/ReachFramework.resources.dll", - "FileType": "NonProduct" - }, - { - "Pattern": "fr/System.Private.ServiceModel.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "fr/System.Web.Services.Description.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "fr/System.Windows.Controls.Ribbon.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "fr/System.Windows.Forms.Design.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "fr/System.Windows.Forms.Primitives.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "fr/System.Windows.Forms.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "fr/System.Windows.Input.Manipulations.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "fr/System.Xaml.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "fr/UIAutomationClient.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "fr/UIAutomationClientSideProviders.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "fr/UIAutomationProvider.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "fr/UIAutomationTypes.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "fr/WindowsBase.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "fr/WindowsFormsIntegration.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "fr\\System.ServiceModel.Http.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "fr\\System.ServiceModel.NetFramingBase.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "fr\\System.ServiceModel.NetTcp.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "fr\\System.ServiceModel.Primitives.resources.dll", + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "getfilesiginforedist.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "getfilesiginforedistwrapper.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "hostfxr.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "hostpolicy.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Humanizer.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "it/Microsoft.CodeAnalysis.CSharp.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "it/Microsoft.CodeAnalysis.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "it/Microsoft.VisualBasic.Forms.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "it/PresentationCore.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "it/PresentationFramework.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "it/PresentationUI.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "it/ReachFramework.resources.dll", - "FileType": "NonProduct" - }, - { - "Pattern": "it/System.Private.ServiceModel.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "it/System.Web.Services.Description.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "it/System.Windows.Controls.Ribbon.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "it/System.Windows.Forms.Design.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "it/System.Windows.Forms.Primitives.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "it/System.Windows.Forms.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "it/System.Windows.Input.Manipulations.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "it/System.Xaml.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "it/UIAutomationClient.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "it/UIAutomationClientSideProviders.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "it/UIAutomationProvider.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "it/UIAutomationTypes.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "it/WindowsBase.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "it/WindowsFormsIntegration.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "it\\System.ServiceModel.Http.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "it\\System.ServiceModel.NetFramingBase.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "it\\System.ServiceModel.NetTcp.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "it\\System.ServiceModel.Primitives.resources.dll", + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ja/Microsoft.CodeAnalysis.CSharp.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ja/Microsoft.CodeAnalysis.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ja/Microsoft.VisualBasic.Forms.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ja/PresentationCore.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ja/PresentationFramework.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ja/PresentationUI.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ja/ReachFramework.resources.dll", - "FileType": "NonProduct" - }, - { - "Pattern": "ja/System.Private.ServiceModel.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ja/System.Web.Services.Description.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ja/System.Windows.Controls.Ribbon.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ja/System.Windows.Forms.Design.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ja/System.Windows.Forms.Primitives.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ja/System.Windows.Forms.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ja/System.Windows.Input.Manipulations.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ja/System.Xaml.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ja/UIAutomationClient.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ja/UIAutomationClientSideProviders.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ja/UIAutomationProvider.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ja/UIAutomationTypes.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ja/WindowsBase.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ja/WindowsFormsIntegration.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "ja\\System.ServiceModel.Http.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "ja\\System.ServiceModel.NetFramingBase.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "ja\\System.ServiceModel.NetTcp.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "ja\\System.ServiceModel.Primitives.resources.dll", + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Json.More.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "JsonPointer.Net.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "JsonSchema.Net.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ko/Microsoft.CodeAnalysis.CSharp.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ko/Microsoft.CodeAnalysis.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ko/Microsoft.VisualBasic.Forms.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ko/PresentationCore.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ko/PresentationFramework.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ko/PresentationUI.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ko/ReachFramework.resources.dll", - "FileType": "NonProduct" - }, - { - "Pattern": "ko/System.Private.ServiceModel.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ko/System.Web.Services.Description.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ko/System.Windows.Controls.Ribbon.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ko/System.Windows.Forms.Design.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ko/System.Windows.Forms.Primitives.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ko/System.Windows.Forms.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ko/System.Windows.Input.Manipulations.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ko/System.Xaml.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ko/UIAutomationClient.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ko/UIAutomationClientSideProviders.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ko/UIAutomationProvider.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ko/UIAutomationTypes.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ko/WindowsBase.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ko/WindowsFormsIntegration.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "ko\\System.ServiceModel.Http.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "ko\\System.ServiceModel.NetFramingBase.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "ko\\System.ServiceModel.NetTcp.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "ko\\System.ServiceModel.Primitives.resources.dll", + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Markdig.Signed.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Microsoft.ApplicationInsights.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Microsoft.Bcl.AsyncInterfaces.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Microsoft.CodeAnalysis.CSharp.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Microsoft.CodeAnalysis.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Microsoft.CSharp.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Microsoft.Extensions.ObjectPool.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Microsoft.Management.Infrastructure.CimCmdlets.xml", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Microsoft.Management.Infrastructure.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Microsoft.Management.Infrastructure.Native.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Microsoft.Management.Infrastructure.Native.Unmanaged.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Microsoft.PowerShell.Commands.Diagnostics.xml", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Microsoft.PowerShell.Commands.Management.xml", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Microsoft.PowerShell.Commands.Utility.xml", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Microsoft.PowerShell.ConsoleHost.xml", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Microsoft.PowerShell.CoreCLR.Eventing.xml", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Microsoft.PowerShell.GraphicalHost.dll.config", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Microsoft.PowerShell.GraphicalHost.xml", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Microsoft.PowerShell.MarkdownRender.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Microsoft.PowerShell.SDK.xml", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Microsoft.PowerShell.Security.xml", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Microsoft.VisualBasic.Core.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Microsoft.VisualBasic.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Microsoft.VisualBasic.Forms.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Microsoft.Win32.Primitives.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Microsoft.Win32.Registry.AccessControl.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Microsoft.Win32.Registry.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Microsoft.Win32.SystemEvents.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Microsoft.WSMan.Management.xml", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Microsoft.WSMan.Runtime.xml", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Modules/Microsoft.PowerShell.Archive/*.cat", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Modules/Microsoft.PowerShell.Archive/*.ps?1", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Modules/Microsoft.PowerShell.PSResourceGet/dependencies/*.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Modules/Microsoft.PowerShell.PSResourceGet/LICENSE", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Modules/Microsoft.PowerShell.PSResourceGet/Microsoft.PowerShell.PSResourceGet.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Modules/Microsoft.PowerShell.PSResourceGet/Microsoft.PowerShell.PSResourceGet.psd1", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Modules/Microsoft.PowerShell.PSResourceGet/Microsoft.PowerShell.PSResourceGet.psm1", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Modules/Microsoft.PowerShell.PSResourceGet/Notice.txt", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Modules/Microsoft.PowerShell.PSResourceGet/PSGet.Format.ps1xml", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Modules/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Modules/PackageManagement/*.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Modules/PackageManagement/*.mof", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Modules/PackageManagement/*.ps?1", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Modules/PackageManagement/*.ps1xml", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Modules/PowerShellGet/*.mfl", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Modules/PowerShellGet/*.mof", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Modules/PowerShellGet/*.ps?1", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Modules/PowerShellGet/*.ps1xml", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Modules/PSReadLine/*.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Modules/PSReadLine/*.ps?1", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Modules/PSReadLine/*.ps1", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Modules/PSReadLine/*.ps1xml", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Modules/PSReadLine/*.txt", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { - "Pattern": "Modules/ThreadJob/*.psd1", - "FileType": "NonProduct" + "Pattern": "Modules\\Microsoft.PowerShell.PSResourceGet\\.signature.p7s", + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Modules\\Microsoft.PowerShell.PSResourceGet\\Microsoft.PowerShell.PSResourceGet.pdb", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Modules\\Microsoft.PowerShell.PSResourceGet\\PSResourceRepository.adml", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Modules\\Microsoft.PowerShell.PSResourceGet\\PSResourceRepository.admx", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Modules\\Microsoft.PowerShell.ThreadJob\\.signature.p7s", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Modules\\Microsoft.PowerShell.ThreadJob\\en-us\\Microsoft.PowerShell.ThreadJob.dll-Help.xml", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Modules\\Microsoft.PowerShell.ThreadJob\\LICENSE", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Modules\\Microsoft.PowerShell.ThreadJob\\ThirdPartyNotices.txt", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Modules\\PSReadLine\\.signature.p7s", - "FileType": "NonProduct" - }, - { - "Pattern": "Modules\\ThreadJob\\.signature.p7s", - "FileType": "NonProduct" - }, - { - "Pattern": "Modules\\ThreadJob\\ThreadJob.psm1", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "mscordaccore_*.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "mscordaccore.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "mscordbi.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "mscorlib.dll", - "FileType": "NonProduct" - }, - { - "Pattern": "mscorrc.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "msquic.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "netstandard.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Newtonsoft.Json.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "PenImc_cor3.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pl/Microsoft.CodeAnalysis.CSharp.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pl/Microsoft.CodeAnalysis.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pl/Microsoft.VisualBasic.Forms.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pl/PresentationCore.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pl/PresentationFramework.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pl/PresentationUI.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pl/ReachFramework.resources.dll", - "FileType": "NonProduct" - }, - { - "Pattern": "pl/System.Private.ServiceModel.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pl/System.Web.Services.Description.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pl/System.Windows.Controls.Ribbon.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pl/System.Windows.Forms.Design.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pl/System.Windows.Forms.Primitives.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pl/System.Windows.Forms.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pl/System.Windows.Input.Manipulations.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pl/System.Xaml.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pl/UIAutomationClient.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pl/UIAutomationClientSideProviders.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pl/UIAutomationProvider.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pl/UIAutomationTypes.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pl/WindowsBase.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pl/WindowsFormsIntegration.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "pl\\System.ServiceModel.Http.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "pl\\System.ServiceModel.NetFramingBase.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "pl\\System.ServiceModel.NetTcp.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "pl\\System.ServiceModel.Primitives.resources.dll", + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "powershell.config.json", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "PowerShell.Core.Instrumentation.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "PowerShell.Core.Instrumentation.man", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "PowerShellCoreExecutionPolicy.adml", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "PowerShellCoreExecutionPolicy.admx", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "PresentationCore.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "PresentationFramework-SystemCore.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "PresentationFramework-SystemData.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "PresentationFramework-SystemDrawing.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "PresentationFramework-SystemXml.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "PresentationFramework-SystemXmlLinq.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "PresentationFramework.Aero.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "PresentationFramework.Aero2.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "PresentationFramework.AeroLite.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "PresentationFramework.Classic.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "PresentationFramework.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "PresentationFramework.Fluent.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "PresentationFramework.Luna.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "PresentationFramework.Royale.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "PresentationNative_cor3.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "PresentationUI.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "preview/pwsh-preview.cmd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "psoptions.json", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pt-BR/Microsoft.CodeAnalysis.CSharp.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pt-BR/Microsoft.CodeAnalysis.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pt-BR/Microsoft.VisualBasic.Forms.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pt-BR/PresentationCore.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pt-BR/PresentationFramework.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pt-BR/PresentationUI.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pt-BR/ReachFramework.resources.dll", - "FileType": "NonProduct" - }, - { - "Pattern": "pt-BR/System.Private.ServiceModel.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pt-BR/System.Web.Services.Description.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pt-BR/System.Windows.Controls.Ribbon.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pt-BR/System.Windows.Forms.Design.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pt-BR/System.Windows.Forms.Primitives.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pt-BR/System.Windows.Forms.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pt-BR/System.Windows.Input.Manipulations.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pt-BR/System.Xaml.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pt-BR/UIAutomationClient.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pt-BR/UIAutomationClientSideProviders.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pt-BR/UIAutomationProvider.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pt-BR/UIAutomationTypes.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pt-BR/WindowsBase.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pt-BR/WindowsFormsIntegration.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "pt-BR\\System.ServiceModel.Http.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "pt-BR\\System.ServiceModel.NetFramingBase.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "pt-BR\\System.ServiceModel.NetTcp.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "pt-BR\\System.ServiceModel.Primitives.resources.dll", + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pwrshplugin.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pwsh.deps.json", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pwsh.runtimeconfig.json", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "pwsh.xml", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ReachFramework.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/Microsoft.CSharp.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/Microsoft.VisualBasic.Core.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/Microsoft.VisualBasic.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/Microsoft.Win32.Primitives.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/Microsoft.Win32.Registry.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/mscorlib.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/netstandard.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.AppContext.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Buffers.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Collections.Concurrent.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Collections.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Collections.Immutable.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Collections.NonGeneric.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Collections.Specialized.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.ComponentModel.Annotations.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.ComponentModel.DataAnnotations.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.ComponentModel.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.ComponentModel.EventBasedAsync.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.ComponentModel.Primitives.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.ComponentModel.TypeConverter.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Configuration.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Console.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Core.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Data.Common.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Data.DataSetExtensions.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Data.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Diagnostics.Contracts.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Diagnostics.Debug.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Diagnostics.DiagnosticSource.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Diagnostics.FileVersionInfo.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Diagnostics.Process.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Diagnostics.StackTrace.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Diagnostics.TextWriterTraceListener.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Diagnostics.Tools.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Diagnostics.TraceSource.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Diagnostics.Tracing.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Drawing.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Drawing.Primitives.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Dynamic.Runtime.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Formats.Asn1.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Formats.Tar.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Globalization.Calendars.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Globalization.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Globalization.Extensions.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.IO.Compression.Brotli.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.IO.Compression.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.IO.Compression.FileSystem.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.IO.Compression.ZipFile.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.IO.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.IO.FileSystem.AccessControl.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.IO.FileSystem.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.IO.FileSystem.DriveInfo.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.IO.FileSystem.Primitives.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.IO.FileSystem.Watcher.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.IO.IsolatedStorage.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.IO.MemoryMappedFiles.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.IO.Pipes.AccessControl.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.IO.Pipes.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.IO.UnmanagedMemoryStream.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Linq.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Linq.Expressions.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Linq.Parallel.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Linq.Queryable.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Memory.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Net.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Net.Http.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Net.Http.Json.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Net.HttpListener.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Net.Mail.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Net.NameResolution.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Net.NetworkInformation.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Net.Ping.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Net.Primitives.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Net.Quic.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Net.Requests.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Net.Security.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Net.ServicePoint.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Net.Sockets.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Net.WebClient.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Net.WebHeaderCollection.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Net.WebProxy.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Net.WebSockets.Client.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Net.WebSockets.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Numerics.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Numerics.Vectors.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.ObjectModel.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Reflection.DispatchProxy.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Reflection.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Reflection.Emit.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Reflection.Emit.ILGeneration.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Reflection.Emit.Lightweight.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Reflection.Extensions.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Reflection.Metadata.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Reflection.Primitives.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Reflection.TypeExtensions.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Resources.Reader.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Resources.ResourceManager.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Resources.Writer.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Runtime.CompilerServices.Unsafe.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Runtime.CompilerServices.VisualC.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Runtime.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Runtime.Extensions.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Runtime.Handles.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Runtime.InteropServices.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Runtime.InteropServices.JavaScript.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Runtime.InteropServices.RuntimeInformation.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Runtime.Intrinsics.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Runtime.Loader.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Runtime.Numerics.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Runtime.Serialization.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Runtime.Serialization.Formatters.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Runtime.Serialization.Json.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Runtime.Serialization.Primitives.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Runtime.Serialization.Xml.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Security.AccessControl.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Security.Claims.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Security.Cryptography.Algorithms.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Security.Cryptography.Cng.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Security.Cryptography.Csp.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Security.Cryptography.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Security.Cryptography.Encoding.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Security.Cryptography.OpenSsl.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Security.Cryptography.Primitives.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Security.Cryptography.X509Certificates.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Security.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Security.Principal.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Security.Principal.Windows.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Security.SecureString.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.ServiceModel.Web.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.ServiceProcess.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Text.Encoding.CodePages.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Text.Encoding.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Text.Encoding.Extensions.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Text.Encodings.Web.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Text.Json.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Text.RegularExpressions.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Threading.Channels.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Threading.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Threading.Overlapped.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Threading.Tasks.Dataflow.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Threading.Tasks.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Threading.Tasks.Extensions.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Threading.Tasks.Parallel.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Threading.Thread.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Threading.ThreadPool.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Threading.Timer.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Transactions.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Transactions.Local.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.ValueTuple.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Web.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Web.HttpUtility.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Windows.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Xml.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Xml.Linq.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Xml.ReaderWriter.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Xml.Serialization.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Xml.XDocument.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Xml.XmlDocument.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Xml.XmlSerializer.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Xml.XPath.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/System.Xml.XPath.XDocument.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref/WindowsBase.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "ref\\System.IO.Compression.Zstandard.dll", + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref\\System.IO.Pipelines.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref\\System.Linq.AsyncEnumerable.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ref\\System.Net.ServerSentEvents.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "ref\\System.Threading.AccessControl.dll", + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ru/Microsoft.CodeAnalysis.CSharp.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ru/Microsoft.CodeAnalysis.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ru/Microsoft.VisualBasic.Forms.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ru/PresentationCore.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ru/PresentationFramework.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ru/PresentationUI.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ru/ReachFramework.resources.dll", - "FileType": "NonProduct" - }, - { - "Pattern": "ru/System.Private.ServiceModel.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ru/System.Web.Services.Description.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ru/System.Windows.Controls.Ribbon.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ru/System.Windows.Forms.Design.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ru/System.Windows.Forms.Primitives.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ru/System.Windows.Forms.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ru/System.Windows.Input.Manipulations.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ru/System.Xaml.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ru/UIAutomationClient.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ru/UIAutomationClientSideProviders.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ru/UIAutomationProvider.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ru/UIAutomationTypes.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ru/WindowsBase.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ru/WindowsFormsIntegration.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "ru\\System.ServiceModel.Http.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "ru\\System.ServiceModel.NetFramingBase.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "ru\\System.ServiceModel.NetTcp.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "ru\\System.ServiceModel.Primitives.resources.dll", + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/base.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/baseConditional.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/block.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/blockCommon.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/blockSoftware.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/command.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/conditionSet.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/developer.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/developerCommand.rld", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/developerCommand.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/developerDscResource.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/developerManaged.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/developerManagedClass.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/developerManagedConstructor.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/developerManagedDelegate.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/developerManagedEnumeration.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/developerManagedEvent.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/developerManagedField.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/developerManagedInterface.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/developerManagedMethod.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/developerManagedNamespace.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/developerManagedOperator.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/developerManagedOverload.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/developerManagedProperty.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/developerManagedStructure.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/developerReference.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/developerStructure.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/developerXaml.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/endUser.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/hierarchy.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/inline.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/inlineCommon.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/inlineSoftware.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/inlineUi.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/ITPro.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/Maml_HTML_Style.xsl", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/Maml_HTML.xsl", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/Maml.rld", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/Maml.tbr", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/Maml.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/Maml.xsx", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/ManagedDeveloper.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/ManagedDeveloperStructure.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/ProviderHelp.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/shellExecute.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/structure.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/structureGlossary.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/structureList.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/structureProcedure.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/structureTable.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/structureTaskExecution.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/task.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Schemas/PSMaml/troubleshooting.xsd", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "sni.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.AppContext.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Buffers.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.CodeDom.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Collections.Concurrent.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Collections.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Collections.Immutable.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Collections.NonGeneric.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Collections.Specialized.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.ComponentModel.Annotations.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.ComponentModel.Composition.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.ComponentModel.Composition.Registration.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.ComponentModel.DataAnnotations.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.ComponentModel.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.ComponentModel.EventBasedAsync.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.ComponentModel.Primitives.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.ComponentModel.TypeConverter.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Configuration.ConfigurationManager.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Configuration.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Console.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Core.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Data.Common.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Data.DataSetExtensions.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Data.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Data.Odbc.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Data.OleDb.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Data.SqlClient.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Design.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Diagnostics.Contracts.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Diagnostics.Debug.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Diagnostics.DiagnosticSource.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Diagnostics.EventLog.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Diagnostics.EventLog.Messages.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Diagnostics.FileVersionInfo.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Diagnostics.PerformanceCounter.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Diagnostics.Process.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Diagnostics.StackTrace.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Diagnostics.TextWriterTraceListener.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Diagnostics.Tools.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Diagnostics.TraceSource.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Diagnostics.Tracing.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.DirectoryServices.AccountManagement.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.DirectoryServices.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.DirectoryServices.Protocols.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Drawing.Common.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Drawing.Design.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Drawing.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Drawing.Primitives.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Dynamic.Runtime.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Formats.Asn1.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Formats.Nrbf.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Formats.Tar.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Globalization.Calendars.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Globalization.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Globalization.Extensions.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.IO.Compression.Brotli.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.IO.Compression.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.IO.Compression.FileSystem.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.IO.Compression.Native.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.IO.Compression.ZipFile.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "System.IO.Compression.Zstandard.dll", + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.IO.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.IO.FileSystem.AccessControl.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.IO.FileSystem.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.IO.FileSystem.DriveInfo.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.IO.FileSystem.Primitives.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.IO.FileSystem.Watcher.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.IO.IsolatedStorage.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.IO.MemoryMappedFiles.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.IO.Packaging.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.IO.Pipelines.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.IO.Pipes.AccessControl.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.IO.Pipes.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.IO.Ports.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.IO.UnmanagedMemoryStream.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Linq.AsyncEnumerable.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Linq.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Linq.Expressions.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Linq.Parallel.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Linq.Queryable.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Management.Automation.xml", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Management.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Memory.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Net.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Net.Http.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Net.Http.Json.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Net.Http.WinHttpHandler.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Net.HttpListener.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Net.Mail.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Net.NameResolution.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Net.NetworkInformation.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Net.Ping.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Net.Primitives.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Net.Quic.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Net.Requests.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Net.Security.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Net.ServerSentEvents.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Net.ServicePoint.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Net.Sockets.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Net.WebClient.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Net.WebHeaderCollection.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Net.WebProxy.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Net.WebSockets.Client.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Net.WebSockets.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Numerics.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Numerics.Vectors.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.ObjectModel.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Printing.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Private.CoreLib.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Private.DataContractSerialization.dll", - "FileType": "NonProduct" - }, - { - "Pattern": "System.Private.ServiceModel.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Private.Uri.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Private.Windows.Core.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Private.Windows.GdiPlus.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Private.Xml.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Private.Xml.Linq.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Reflection.Context.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Reflection.DispatchProxy.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Reflection.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Reflection.Emit.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Reflection.Emit.ILGeneration.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Reflection.Emit.Lightweight.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Reflection.Extensions.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Reflection.Metadata.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Reflection.Primitives.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Reflection.TypeExtensions.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Resources.Extensions.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Resources.Reader.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Resources.ResourceManager.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Resources.Writer.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Runtime.Caching.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Runtime.CompilerServices.Unsafe.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Runtime.CompilerServices.VisualC.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Runtime.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Runtime.Extensions.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Runtime.Handles.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Runtime.InteropServices.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Runtime.InteropServices.JavaScript.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Runtime.InteropServices.RuntimeInformation.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Runtime.Intrinsics.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Runtime.Loader.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Runtime.Numerics.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Runtime.Serialization.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Runtime.Serialization.Formatters.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Runtime.Serialization.Json.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Runtime.Serialization.Primitives.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Runtime.Serialization.Xml.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Security.AccessControl.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Security.Claims.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Security.Cryptography.Algorithms.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Security.Cryptography.Cng.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Security.Cryptography.Csp.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Security.Cryptography.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Security.Cryptography.Encoding.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Security.Cryptography.OpenSsl.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Security.Cryptography.Pkcs.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Security.Cryptography.Primitives.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Security.Cryptography.ProtectedData.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Security.Cryptography.X509Certificates.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Security.Cryptography.Xml.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Security.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Security.Permissions.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Security.Principal.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Security.Principal.Windows.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Security.SecureString.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.ServiceModel.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.ServiceModel.Duplex.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.ServiceModel.Http.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "System.ServiceModel.NetFramingBase.dll", + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.ServiceModel.NetTcp.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.ServiceModel.Primitives.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.ServiceModel.Security.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.ServiceModel.Syndication.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.ServiceModel.Web.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.ServiceProcess.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.ServiceProcess.ServiceController.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Speech.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Text.Encoding.CodePages.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Text.Encoding.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Text.Encoding.Extensions.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Text.Encodings.Web.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Text.Json.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Text.RegularExpressions.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Threading.AccessControl.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Threading.Channels.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Threading.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Threading.Overlapped.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Threading.Tasks.Dataflow.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Threading.Tasks.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Threading.Tasks.Extensions.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Threading.Tasks.Parallel.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Threading.Thread.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Threading.ThreadPool.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Threading.Timer.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Transactions.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Transactions.Local.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.ValueTuple.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Web.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Web.HttpUtility.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Web.Services.Description.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Windows.Controls.Ribbon.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Windows.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Windows.Extensions.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Windows.Forms.Design.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Windows.Forms.Design.Editors.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Windows.Forms.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Windows.Forms.Primitives.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Windows.Input.Manipulations.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Windows.Presentation.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Windows.Primitives.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Xaml.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Xml.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Xml.Linq.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Xml.ReaderWriter.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Xml.Serialization.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Xml.XDocument.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Xml.XmlDocument.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Xml.XmlSerializer.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Xml.XPath.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "System.Xml.XPath.XDocument.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "ThirdPartyNotices.txt", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "tr/Microsoft.CodeAnalysis.CSharp.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "tr/Microsoft.CodeAnalysis.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "tr/Microsoft.VisualBasic.Forms.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "tr/PresentationCore.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "tr/PresentationFramework.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "tr/PresentationUI.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "tr/ReachFramework.resources.dll", - "FileType": "NonProduct" - }, - { - "Pattern": "tr/System.Private.ServiceModel.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "tr/System.Web.Services.Description.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "tr/System.Windows.Controls.Ribbon.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "tr/System.Windows.Forms.Design.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "tr/System.Windows.Forms.Primitives.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "tr/System.Windows.Forms.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "tr/System.Windows.Input.Manipulations.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "tr/System.Xaml.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "tr/UIAutomationClient.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "tr/UIAutomationClientSideProviders.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "tr/UIAutomationProvider.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "tr/UIAutomationTypes.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "tr/WindowsBase.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "tr/WindowsFormsIntegration.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "tr\\System.ServiceModel.Http.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "tr\\System.ServiceModel.NetFramingBase.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "tr\\System.ServiceModel.NetTcp.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "tr\\System.ServiceModel.Primitives.resources.dll", + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "UIAutomationClient.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "UIAutomationClientSideProviders.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "UIAutomationProvider.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "UIAutomationTypes.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "vcruntime140_cor3.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "WindowsBase.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "WindowsFormsIntegration.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "wpfgfx_cor3.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hans/Microsoft.CodeAnalysis.CSharp.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hans/Microsoft.CodeAnalysis.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hans/Microsoft.VisualBasic.Forms.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hans/PresentationCore.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hans/PresentationFramework.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hans/PresentationUI.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hans/ReachFramework.resources.dll", - "FileType": "NonProduct" - }, - { - "Pattern": "zh-Hans/System.Private.ServiceModel.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hans/System.Web.Services.Description.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hans/System.Windows.Controls.Ribbon.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hans/System.Windows.Forms.Design.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hans/System.Windows.Forms.Primitives.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hans/System.Windows.Forms.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hans/System.Windows.Input.Manipulations.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hans/System.Xaml.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hans/UIAutomationClient.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hans/UIAutomationClientSideProviders.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hans/UIAutomationProvider.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hans/UIAutomationTypes.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hans/WindowsBase.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hans/WindowsFormsIntegration.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "zh-Hans\\System.ServiceModel.Http.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "zh-Hans\\System.ServiceModel.NetFramingBase.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "zh-Hans\\System.ServiceModel.NetTcp.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "zh-Hans\\System.ServiceModel.Primitives.resources.dll", + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hant/Microsoft.CodeAnalysis.CSharp.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hant/Microsoft.CodeAnalysis.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hant/Microsoft.VisualBasic.Forms.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hant/PresentationCore.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hant/PresentationFramework.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hant/PresentationUI.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hant/ReachFramework.resources.dll", - "FileType": "NonProduct" - }, - { - "Pattern": "zh-Hant/System.Private.ServiceModel.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hant/System.Web.Services.Description.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hant/System.Windows.Controls.Ribbon.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hant/System.Windows.Forms.Design.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hant/System.Windows.Forms.Primitives.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hant/System.Windows.Forms.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hant/System.Windows.Input.Manipulations.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hant/System.Xaml.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hant/UIAutomationClient.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hant/UIAutomationClientSideProviders.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hant/UIAutomationProvider.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hant/UIAutomationTypes.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hant/WindowsBase.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "zh-Hant/WindowsFormsIntegration.resources.dll", - "FileType": "NonProduct" + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "zh-Hant\\System.ServiceModel.Http.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "zh-Hant\\System.ServiceModel.NetFramingBase.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "zh-Hant\\System.ServiceModel.NetTcp.resources.dll", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "zh-Hant\\System.ServiceModel.Primitives.resources.dll", + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "_manifest/spdx_2.2/manifest.spdx.json", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "_manifest/spdx_2.2/manifest.spdx.json.sha256", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "assets/Powershell_av_colors.ico", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "assets/Powershell_avatar.ico", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "assets/Powershell_black.ico", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "assets/ps_black_32x32.ico", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "Install-PowerShellRemoting.ps1", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "InstallPSCorePolicyDefinitions.ps1", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "LICENSE.txt", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "Microsoft.Management.Infrastructure.CimCmdlets.dll", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "Microsoft.PowerShell.Commands.Diagnostics.dll", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "Microsoft.PowerShell.Commands.Management.dll", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "Microsoft.PowerShell.Commands.Utility.dll", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "Microsoft.PowerShell.ConsoleHost.dll", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "Microsoft.PowerShell.CoreCLR.Eventing.dll", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "Microsoft.PowerShell.GraphicalHost.dll", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "Microsoft.PowerShell.SDK.dll", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "Microsoft.PowerShell.Security.dll", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "Microsoft.WSMan.Management.dll", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "Microsoft.WSMan.Runtime.dll", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "Modules/CimCmdlets/CimCmdlets.psd1", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "Modules/Microsoft.PowerShell.Diagnostics/Diagnostics.format.ps1xml", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "Modules/Microsoft.PowerShell.Diagnostics/Event.format.ps1xml", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "Modules/Microsoft.PowerShell.Diagnostics/GetEvent.types.ps1xml", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "Modules/Microsoft.PowerShell.Diagnostics/Microsoft.PowerShell.Diagnostics.psd1", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "Modules/Microsoft.PowerShell.Host/Microsoft.PowerShell.Host.psd1", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "Modules/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "Modules/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "Modules/Microsoft.PowerShell.Security/Security.types.ps1xml", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "Modules/Microsoft.WSMan.Management/Microsoft.WSMan.Management.psd1", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "Modules/Microsoft.WSMan.Management/WSMan.format.ps1xml", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "Modules/PSDiagnostics/PSDiagnostics.psd1", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "Modules/PSDiagnostics/PSDiagnostics.psm1", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "Modules\\Microsoft.PowerShell.PSResourceGet\\InstallPSResourceGetPolicyDefinitions.ps1", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "Modules\\Microsoft.PowerShell.ThreadJob\\Microsoft.PowerShell.ThreadJob.dll", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "Modules\\Microsoft.PowerShell.ThreadJob\\Microsoft.PowerShell.ThreadJob.psd1", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "pwsh.dll", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { "Pattern": "pwsh.exe", - "FileType": "Product" + "FileType": "Product", + "Architecture": null }, { - "Pattern": "RegisterManifest.ps1", - "FileType": "Product" + "Pattern": "pwsh.profile.dsc.resource.json", + "FileType": "Product", + "Architecture": null + }, + { + "Pattern": "pwsh.profile.resource.ps1", + "FileType": "Product", + "Architecture": null }, { - "Pattern": "RegisterMicrosoftUpdate.ps1", - "FileType": "Product" + "Pattern": "RegisterManifest.ps1", + "FileType": "Product", + "Architecture": null }, { "Pattern": "System.Management.Automation.dll", - "FileType": "Product" + "FileType": "Product", + "Architecture": null } ] diff --git a/tools/packaging/packaging.psd1 b/tools/packaging/packaging.psd1 index 0053428a481..d7c27b2c3cc 100644 --- a/tools/packaging/packaging.psd1 +++ b/tools/packaging/packaging.psd1 @@ -7,13 +7,10 @@ PowerShellVersion = "5.0" CmdletsToExport = @() FunctionsToExport = @( - 'Compress-ExePackageEngine' - 'Expand-ExePackageEngine' 'Expand-PSSignedBuild' 'Invoke-AzDevOpsLinuxPackageBuild' 'Invoke-AzDevOpsLinuxPackageCreation' 'New-DotnetSdkContainerFxdPackage' - 'New-ExePackage' 'Start-PrepForGlobalToolNupkg' 'New-GlobalToolNupkgSource' 'New-GlobalToolNupkgFromSource' @@ -26,6 +23,7 @@ 'Test-PackageManifest' 'Update-PSSignedBuildFolder' 'Test-Bom' + 'Get-MacOSPackageIdentifierInfo' ) RootModule = "packaging.psm1" RequiredModules = @("build") diff --git a/tools/packaging/packaging.psm1 b/tools/packaging/packaging.psm1 index 52355c1eb4f..39127804b17 100644 --- a/tools/packaging/packaging.psm1 +++ b/tools/packaging/packaging.psm1 @@ -1,6 +1,8 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. +. "$PSScriptRoot\..\buildCommon\startNativeExecution.ps1" + $Environment = Get-EnvironmentInformation $RepoRoot = (Resolve-Path -Path "$PSScriptRoot/../..").Path @@ -16,7 +18,7 @@ $AllDistributions = @() $AllDistributions += $DebianDistributions $AllDistributions += $RedhatDistributions $AllDistributions += 'macOs' -$script:netCoreRuntime = 'net10.0' +$script:netCoreRuntime = 'net11.0' $script:iconFileName = "Powershell_black_64.png" $script:iconPath = Join-Path -path $PSScriptRoot -ChildPath "../../assets/$iconFileName" -Resolve @@ -50,7 +52,7 @@ function Start-PSPackage { [string]$Name = "powershell", # Ubuntu, CentOS, Fedora, macOS, and Windows packages are supported - [ValidateSet("msix", "deb", "osxpkg", "rpm", "rpm-fxdependent", "rpm-fxdependent-arm64", "msi", "zip", "zip-pdb", "tar", "tar-arm", "tar-arm64", "tar-alpine", "fxdependent", "fxdependent-win-desktop", "min-size", "tar-alpine-fxdependent")] + [ValidateSet("msix", "deb", "deb-arm64", "osxpkg", "rpm", "rpm-fxdependent", "rpm-fxdependent-arm64", "zip", "zip-pdb", "tar", "tar-arm", "tar-arm64", "tar-alpine", "fxdependent", "fxdependent-win-desktop", "min-size", "tar-alpine-fxdependent")] [string[]]$Type, # Generate windows downlevel package @@ -119,6 +121,9 @@ function Start-PSPackage { elseif ($Type.Count -eq 1 -and $Type[0] -eq "tar-alpine-fxdependent") { New-PSOptions -Configuration "Release" -Runtime 'fxdependent-noopt-linux-musl-x64' -WarningAction SilentlyContinue | ForEach-Object { $_.Runtime, $_.Configuration } } + elseif ($Type.Count -eq 1 -and $Type[0] -eq "deb-arm64") { + New-PSOptions -Configuration "Release" -Runtime 'linux-arm64' -WarningAction SilentlyContinue | ForEach-Object { $_.Runtime, $_.Configuration } + } else { New-PSOptions -Configuration "Release" -WarningAction SilentlyContinue | ForEach-Object { $_.Runtime, $_.Configuration } } @@ -282,6 +287,18 @@ function Start-PSPackage { $createdSpdxPathSha = New-Item -Path $manifestSpdxPathSha -Force Write-Verbose -Verbose "Created manifest.spdx.json.sha256 file: $createdSpdxPathSha" } + + $bsiJsonPath = (Join-Path -Path $Source "_manifest\spdx_2.2\bsi.json") + if (-not (Test-Path -Path $bsiJsonPath)) { + $createdBsiJsonPath = New-Item -Path $bsiJsonPath -Force + Write-Verbose -Verbose "Created bsi.json file: $createdBsiJsonPath" + } + + $manifestCatPath = (Join-Path -Path $Source "_manifest\spdx_2.2\manifest.cat") + if (-not (Test-Path -Path $manifestCatPath)) { + $createdCatPath = New-Item -Path $manifestCatPath -Force + Write-Verbose -Verbose "Created manifest.cat file: $createdCatPath" + } } # If building a symbols package, we add a zip of the parent to publish @@ -332,7 +349,7 @@ function Start-PSPackage { } elseif ($Environment.IsMacOS) { "osxpkg", "tar" } elseif ($Environment.IsWindows) { - "msi", "msix" + "zip", "msix" } Write-Warning "-Type was not specified, continuing with $Type!" } @@ -479,34 +496,6 @@ function Start-PSPackage { } } } - "msi" { - $TargetArchitecture = "x64" - $r2rArchitecture = "amd64" - if ($Runtime -match "-x86") { - $TargetArchitecture = "x86" - $r2rArchitecture = "i386" - } - elseif ($Runtime -match "-arm64") - { - $TargetArchitecture = "arm64" - $r2rArchitecture = "arm64" - } - - Write-Verbose "TargetArchitecture = $TargetArchitecture" -Verbose - - $Arguments = @{ - ProductNameSuffix = $NameSuffix - ProductSourcePath = $Source - ProductVersion = $Version - AssetsPath = "$RepoRoot\assets" - ProductTargetArchitecture = $TargetArchitecture - Force = $Force - } - - if ($PSCmdlet.ShouldProcess("Create MSI Package")) { - New-MSIPackage @Arguments - } - } "msix" { $Arguments = @{ ProductNameSuffix = $NameSuffix @@ -515,6 +504,7 @@ function Start-PSPackage { Architecture = $WindowsRuntime.Split('-')[1] Force = $Force Private = $Private + LTS = $LTS } if ($PSCmdlet.ShouldProcess("Create MSIX Package")) { @@ -626,6 +616,24 @@ function Start-PSPackage { } } } + 'deb-arm64' { + $Arguments = @{ + Type = 'deb' + PackageSourcePath = $Source + Name = $Name + Version = $Version + Force = $Force + NoSudo = $NoSudo + LTS = $LTS + HostArchitecture = "arm64" + } + foreach ($Distro in $Script:DebianDistributions) { + $Arguments["Distribution"] = $Distro + if ($PSCmdlet.ShouldProcess("Create DEB Package for $Distro")) { + New-UnixPackage @Arguments + } + } + } 'rpm' { $Arguments = @{ Type = 'rpm' @@ -789,6 +797,18 @@ function New-TarballPackage { $Staging = "$PSScriptRoot/staging" New-StagingFolder -StagingPath $Staging -PackageSourcePath $PackageSourcePath -R2RVerification $R2RVerification + # Ensure PowerShell executable has correct permissions in tarball + $pwshInStaging = Join-Path $Staging 'pwsh' + if (Test-Path -LiteralPath $pwshInStaging) { + Start-NativeExecution { chmod 755 $pwshInStaging } + } + + # Included .NET executable for producing crash dumps + $createdumpInStaging = Join-Path $Staging 'createdump' + if (Test-Path -LiteralPath $createdumpInStaging) { + Start-NativeExecution { chmod 755 $createdumpInStaging } + } + if (Get-Command -Name tar -CommandType Application -ErrorAction Ignore) { if ($Force -or $PSCmdlet.ShouldProcess("Create tarball package")) { $options = "-czf" @@ -887,7 +907,8 @@ function Update-PSSignedBuildFolder [string]$BuildPath, [Parameter(Mandatory)] [string]$SignedFilesPath, - [string[]] $RemoveFilter = ('*.pdb', '*.zip', '*.r2rmap') + [string[]] $RemoveFilter = ('*.pdb', '*.zip', '*.r2rmap'), + [bool]$OfficialBuild = $true ) $BuildPathNormalized = (Get-Item $BuildPath).FullName @@ -943,8 +964,21 @@ function Update-PSSignedBuildFolder if ($IsWindows) { $signature = Get-AuthenticodeSignature -FilePath $signedFilePath - if ($signature.Status -ne 'Valid') { + + if ($signature.Status -ne 'Valid' -and $OfficialBuild) { + Write-Host "Certificate Issuer: $($signature.SignerCertificate.Issuer)" + Write-Host "Certificate Subject: $($signature.SignerCertificate.Subject)" Write-Error "Invalid signature for $signedFilePath" + } elseif ($OfficialBuild -eq $false) { + if ($signature.Status -eq 'NotSigned') { + Write-Warning "File is not signed: $signedFilePath" + } elseif ($signature.SignerCertificate.Issuer -notmatch '^CN=(Microsoft|TestAzureEngBuildCodeSign|Windows Internal Build Tools).*') { + Write-Warning "File signed with test certificate: $signedFilePath" + Write-Host "Certificate Issuer: $($signature.SignerCertificate.Issuer)" + Write-Host "Certificate Subject: $($signature.SignerCertificate.Subject)" + } else { + Write-Verbose -Verbose "File properly signed: $signedFilePath" + } } } else @@ -1031,7 +1065,7 @@ function New-UnixPackage { # This is a string because strings are appended to it [string]$Iteration = "1", - # Host architecture values allowed for deb type packages: amd64 + # Host architecture values allowed for deb type packages: amd64, arm64 # Host architecture values allowed for rpm type packages include: x86_64, aarch64, native, all, noarch, any # Host architecture values allowed for osxpkg type packages include: x86_64, arm64 [string] @@ -1054,7 +1088,7 @@ function New-UnixPackage { DynamicParam { if ($Type -eq "deb" -or $Type -like 'rpm*') { # Add a dynamic parameter '-Distribution' when the specified package type is 'deb'. - # The '-Distribution' parameter can be used to indicate which Debian distro this pacakge is targeting. + # The '-Distribution' parameter can be used to indicate which Debian distro this package is targeting. $ParameterAttr = New-Object "System.Management.Automation.ParameterAttribute" if($type -eq 'deb') { @@ -1132,11 +1166,12 @@ function New-UnixPackage { # Determine if the version is a preview version $IsPreview = Test-IsPreview -Version $Version -IsLTS:$LTS - # Preview versions have preview in the name + # For deb/rpm packages, use the '-lts' and '-preview' channel suffix variants to match existing names on packages.microsoft.com. + # For osxpkg package, only LTS packages get a channel suffix in the name. $Name = if($LTS) { "powershell-lts" } - elseif ($IsPreview) { + elseif ($IsPreview -and $Type -ne "osxpkg") { "powershell-preview" } else { @@ -1182,20 +1217,6 @@ function New-UnixPackage { # Generate After Install and After Remove scripts $AfterScriptInfo = New-AfterScripts -Link $Link -Distribution $DebDistro -Destination $Destination - # there is a weird bug in fpm - # if the target of the powershell symlink exists, `fpm` aborts - # with a `utime` error on macOS. - # so we move it to make symlink broken - # refers to executable, does not vary by channel - $symlink_dest = "$Destination/pwsh" - $hack_dest = "./_fpm_symlink_hack_powershell" - if ($Environment.IsMacOS) { - if (Test-Path $symlink_dest) { - Write-Warning "Move $symlink_dest to $hack_dest (fpm utime bug)" - Start-NativeExecution ([ScriptBlock]::Create("$sudo mv $symlink_dest $hack_dest")) - } - } - # Generate gzip of man file $ManGzipInfo = New-ManGzip -IsPreview:$IsPreview -IsLTS:$LTS @@ -1206,7 +1227,11 @@ function New-UnixPackage { find $Staging -type f | xargs chmod 644 chmod 644 $ManGzipInfo.GzipFile # refers to executable, does not vary by channel - chmod 755 "$Staging/pwsh" #only the executable file should be granted the execution permission + chmod 755 "$Staging/pwsh" # only the executable file should be granted the execution permission + # Included .NET executable for producing crash dumps + if (Test-Path "$Staging/createdump") { + chmod 755 "$Staging/createdump" + } } } @@ -1230,41 +1255,151 @@ function New-UnixPackage { # Setup package dependencies $Dependencies = @(Get-PackageDependencies @packageDependenciesParams) - $Arguments = @() - - - $Arguments += Get-FpmArguments ` - -Name $Name ` - -Version $packageVersion ` - -Iteration $Iteration ` - -Description $Description ` - -Type $Type ` - -Dependencies $Dependencies ` - -AfterInstallScript $AfterScriptInfo.AfterInstallScript ` - -AfterRemoveScript $AfterScriptInfo.AfterRemoveScript ` - -Staging $Staging ` - -Destination $Destination ` - -ManGzipFile $ManGzipInfo.GzipFile ` - -ManDestination $ManGzipInfo.ManFile ` - -LinkInfo $Links ` - -AppsFolder $AppsFolder ` - -Distribution $DebDistro ` - -HostArchitecture $HostArchitecture ` - -ErrorAction Stop - # Build package try { - if ($PSCmdlet.ShouldProcess("Create $type package")) { - Write-Log "Creating package with fpm $Arguments..." - try { - $Output = Start-NativeExecution { fpm $Arguments } + if ($Type -eq 'rpm') { + # Use rpmbuild directly for RPM packages + if ($PSCmdlet.ShouldProcess("Create RPM package with rpmbuild")) { + Write-Log "Creating RPM package with rpmbuild..." + + # Create rpmbuild directory structure + $rpmBuildRoot = Join-Path $env:HOME "rpmbuild" + $specsDir = Join-Path $rpmBuildRoot "SPECS" + $rpmsDir = Join-Path $rpmBuildRoot "RPMS" + + New-Item -ItemType Directory -Path $specsDir -Force | Out-Null + New-Item -ItemType Directory -Path $rpmsDir -Force | Out-Null + + # Generate RPM spec file + $specContent = New-RpmSpec ` + -Name $Name ` + -Version $packageVersion ` + -Iteration $Iteration ` + -Description $Description ` + -Dependencies $Dependencies ` + -AfterInstallScript $AfterScriptInfo.AfterInstallScript ` + -AfterRemoveScript $AfterScriptInfo.AfterRemoveScript ` + -Staging $Staging ` + -Destination $Destination ` + -ManGzipFile $ManGzipInfo.GzipFile ` + -ManDestination $ManGzipInfo.ManFile ` + -LinkInfo $Links ` + -Distribution $DebDistro ` + -HostArchitecture $HostArchitecture + + $specFile = Join-Path $specsDir "$Name.spec" + $specContent | Out-File -FilePath $specFile -Encoding ascii + Write-Verbose "Generated spec file: $specFile" -Verbose + + # Log the spec file content + if ($env:GITHUB_ACTIONS -eq 'true') { + Write-Host "::group::RPM Spec File Content" + Write-Host $specContent + Write-Host "::endgroup::" + } else { + Write-Verbose "RPM Spec File Content:`n$specContent" -Verbose + } + + # Build RPM package + try { + # Use bash to properly handle rpmbuild arguments + # Add --target for cross-architecture builds + $targetArch = "" + if ($HostArchitecture -ne "x86_64" -and $HostArchitecture -ne "noarch") { + $targetArch = "--target $HostArchitecture" + } + $buildCmd = "rpmbuild -bb --quiet $targetArch --define '_topdir $rpmBuildRoot' --buildroot '$rpmBuildRoot/BUILDROOT' '$specFile'" + Write-Verbose "Running: $buildCmd" -Verbose + $Output = bash -c $buildCmd 2>&1 + $exitCode = $LASTEXITCODE + + if ($exitCode -ne 0) { + throw "rpmbuild failed with exit code $exitCode" + } + + # Find the generated RPM + $rpmFile = Get-ChildItem -Path (Join-Path $rpmsDir $HostArchitecture) -Filter "*.rpm" -ErrorAction Stop | + Sort-Object -Property LastWriteTime -Descending | + Select-Object -First 1 + + if ($rpmFile) { + # Copy RPM to current location + Copy-Item -Path $rpmFile.FullName -Destination $CurrentLocation -Force + $Output = @("Created package {:path=>""$($rpmFile.Name)""}") + } else { + throw "RPM file not found after build" + } + } + catch { + Write-Verbose -Message "!!!Handling error in rpmbuild!!!" -Verbose -ErrorAction SilentlyContinue + if ($Output) { + Write-Verbose -Message "$Output" -Verbose -ErrorAction SilentlyContinue + } + Get-Error -InputObject $_ + throw + } + } + } elseif ($Type -eq 'deb') { + # Use native DEB package builder + if ($PSCmdlet.ShouldProcess("Create DEB package natively")) { + Write-Log "Creating DEB package natively..." + try { + $result = New-NativeDeb ` + -Name $Name ` + -Version $packageVersion ` + -Iteration $Iteration ` + -Description $Description ` + -Staging $Staging ` + -Destination $Destination ` + -ManGzipFile $ManGzipInfo.GzipFile ` + -ManDestination $ManGzipInfo.ManFile ` + -LinkInfo $Links ` + -Dependencies $Dependencies ` + -AfterInstallScript $AfterScriptInfo.AfterInstallScript ` + -AfterRemoveScript $AfterScriptInfo.AfterRemoveScript ` + -HostArchitecture $HostArchitecture ` + -CurrentLocation $CurrentLocation + + $Output = @("Created package {:path=>""$($result.PackageName)""}") + } + catch { + Write-Verbose -Message "!!!Handling error in native DEB creation!!!" -Verbose -ErrorAction SilentlyContinue + } } - catch { - Write-Verbose -Message "!!!Handling error in FPM!!!" -Verbose -ErrorAction SilentlyContinue - Write-Verbose -Message "$Output" -Verbose -ErrorAction SilentlyContinue - Get-Error -InputObject $_ - throw + } elseif ($Type -eq 'osxpkg') { + # Use native macOS packaging tools + if ($PSCmdlet.ShouldProcess("Create macOS package with pkgbuild/productbuild")) { + Write-Log "Creating macOS package with native tools..." + + $macPkgArgs = @{ + Name = $Name + Version = $packageVersion + Iteration = $Iteration + Staging = $Staging + Destination = $Destination + ManGzipFile = $ManGzipInfo.GzipFile + ManDestination = $ManGzipInfo.ManFile + LinkInfo = $Links + AfterInstallScript = $AfterScriptInfo.AfterInstallScript + AppsFolder = $AppsFolder + HostArchitecture = $HostArchitecture + CurrentLocation = $CurrentLocation + LTS = $LTS + } + + try { + $packageFile = New-MacOSPackage @macPkgArgs + $Output = @("Created package {:path=>""$($packageFile.Name)""}") + } + catch { + Write-Verbose -Message "!!!Handling error in macOS packaging!!!" -Verbose -ErrorAction SilentlyContinue + Get-Error -InputObject $_ + throw + } } + } else { + # Nothing should reach here + throw "Unknown package type: $Type" } } finally { if ($Environment.IsMacOS) { @@ -1273,13 +1408,17 @@ function New-UnixPackage { { Clear-MacOSLauncher } + } - # this is continuation of a fpm hack for a weird bug - if (Test-Path $hack_dest) { - Write-Warning "Move $hack_dest to $symlink_dest (fpm utime bug)" - Start-NativeExecution -sb ([ScriptBlock]::Create("$sudo mv $hack_dest $symlink_dest")) -VerboseOutputOnError + # Clean up rpmbuild directory if it was created + if ($Type -eq 'rpm') { + $rpmBuildRoot = Join-Path $env:HOME "rpmbuild" + if (Test-Path $rpmBuildRoot) { + Write-Verbose "Cleaning up rpmbuild directory: $rpmBuildRoot" -Verbose + Remove-Item -Path $rpmBuildRoot -Recurse -Force -ErrorAction SilentlyContinue } } + if ($AfterScriptInfo.AfterInstallScript) { Remove-Item -ErrorAction 'silentlycontinue' $AfterScriptInfo.AfterInstallScript -Force } @@ -1292,12 +1431,8 @@ function New-UnixPackage { # Magic to get path output $createdPackage = Get-Item (Join-Path $CurrentLocation (($Output[-1] -split ":path=>")[-1] -replace '["{}]')) - if ($Environment.IsMacOS) { - if ($PSCmdlet.ShouldProcess("Add distribution information and Fix PackageName")) - { - $createdPackage = New-MacOsDistributionPackage -FpmPackage $createdPackage -HostArchitecture $HostArchitecture -IsPreview:$IsPreview - } - } + # For macOS with native tools, the package is already in the correct format + # For other platforms, the package name from dpkg-deb/rpmbuild is sufficient if (Test-Path $createdPackage) { @@ -1342,14 +1477,27 @@ Function New-LinkInfo function New-MacOsDistributionPackage { + [CmdletBinding(SupportsShouldProcess=$true)] param( - [Parameter(Mandatory,HelpMessage='The FileInfo of the file created by FPM')] - [System.IO.FileInfo]$FpmPackage, + [Parameter(Mandatory,HelpMessage='The FileInfo of the component package')] + [System.IO.FileInfo]$ComponentPackage, + + [Parameter(Mandatory,HelpMessage='Package name for the output file')] + [string]$PackageName, + + [Parameter(Mandatory,HelpMessage='Package version')] + [string]$Version, + + [Parameter(Mandatory,HelpMessage='Output directory for the final package')] + [string]$OutputDirectory, [Parameter(HelpMessage='x86_64 for Intel or arm64 for Apple Silicon')] [ValidateSet("x86_64", "arm64")] [string] $HostArchitecture = "x86_64", + [Parameter(HelpMessage='Package identifier')] + [string]$PackageIdentifier, + [Switch] $IsPreview ) @@ -1358,64 +1506,88 @@ function New-MacOsDistributionPackage throw 'New-MacOsDistributionPackage is only supported on macOS!' } - $packageName = Split-Path -Leaf -Path $FpmPackage - # Create a temp directory to store the needed files $tempDir = Join-Path ([System.IO.Path]::GetTempPath()) ([System.IO.Path]::GetRandomFileName()) New-Item -ItemType Directory -Path $tempDir -Force > $null $resourcesDir = Join-Path -Path $tempDir -ChildPath 'resources' New-Item -ItemType Directory -Path $resourcesDir -Force > $null - #Copy background file to temp directory - $backgroundFile = "$RepoRoot/assets/macDialog.png" - Copy-Item -Path $backgroundFile -Destination $resourcesDir - # Move the current package to the temp directory - $tempPackagePath = Join-Path -Path $tempDir -ChildPath $packageName - Move-Item -Path $FpmPackage -Destination $tempPackagePath -Force - - # Add the OS information to the macOS package file name. - $packageExt = [System.IO.Path]::GetExtension($FpmPackage.Name) - - # get the package name from fpm without the extension, but replace powershell-preview at the beginning of the name with powershell. - $packageNameWithoutExt = [System.IO.Path]::GetFileNameWithoutExtension($FpmPackage.Name) -replace '^powershell\-preview' , 'powershell' - $newPackageName = "{0}-{1}{2}" -f $packageNameWithoutExt, $script:Options.Runtime, $packageExt - $newPackagePath = Join-Path $FpmPackage.DirectoryName $newPackageName - - # -Force is not deleting the NewName if it exists, so delete it if it does - if ($Force -and (Test-Path -Path $newPackagePath)) - { - Remove-Item -Force $newPackagePath + # Copy background file to temp directory + $backgroundFile = "$RepoRoot/assets/macDialog.png" + if (Test-Path $backgroundFile) { + Copy-Item -Path $backgroundFile -Destination $resourcesDir -Force } + # Copy the component package to temp directory + $componentFileName = Split-Path -Leaf -Path $ComponentPackage + $tempComponentPath = Join-Path -Path $tempDir -ChildPath $componentFileName + Copy-Item -Path $ComponentPackage -Destination $tempComponentPath -Force + # Create the distribution xml $distributionXmlPath = Join-Path -Path $tempDir -ChildPath 'powershellDistribution.xml' - $packageId = Get-MacOSPackageId -IsPreview:$IsPreview.IsPresent + # Get package ID if not provided + if (-not $PackageIdentifier) { + if ($IsPreview.IsPresent) { + $PackageIdentifier = 'com.microsoft.powershell-preview' + } + else { + $PackageIdentifier = 'com.microsoft.powershell' + } + } + + # Minimum OS version + $minOSVersion = "11.0" # macOS Big Sur minimum # format distribution template with: # 0 - title # 1 - version - # 2 - package path + # 2 - package path (component package filename) # 3 - minimum os version # 4 - Package Identifier # 5 - host architecture (x86_64 for Intel or arm64 for Apple Silicon) - $PackagingStrings.OsxDistributionTemplate -f "PowerShell - $packageVersion", $packageVersion, $packageName, '10.14', $packageId, $HostArchitecture | Out-File -Encoding ascii -FilePath $distributionXmlPath -Force + $PackagingStrings.OsxDistributionTemplate -f $PackageName, $Version, $componentFileName, $minOSVersion, $PackageIdentifier, $HostArchitecture | Out-File -Encoding utf8 -FilePath $distributionXmlPath -Force - Write-Log "Applying distribution.xml to package..." - Push-Location $tempDir - try - { - # productbuild is an xcode command line tool, and those tools are installed when you install brew - Start-NativeExecution -sb {productbuild --distribution $distributionXmlPath --resources $resourcesDir $newPackagePath} -VerboseOutputOnError + # Build final package path + # Rename x86_64 to x64 for compatibility + $packageArchName = if ($HostArchitecture -eq "x86_64") { "x64" } else { $HostArchitecture } + $finalPackagePath = Join-Path $OutputDirectory "$PackageName-$Version-osx-$packageArchName.pkg" + + # Remove existing package if it exists + if (Test-Path $finalPackagePath) { + Write-Warning "Removing existing package: $finalPackagePath" + Remove-Item $finalPackagePath -Force } - finally - { - Pop-Location - Remove-Item -Path $tempDir -Recurse -Force + + if ($PSCmdlet.ShouldProcess("Build product package with productbuild")) { + Write-Log "Applying distribution.xml to package..." + Push-Location $tempDir + try + { + # productbuild is an xcode command line tool + Start-NativeExecution -VerboseOutputOnError { + productbuild --distribution $distributionXmlPath ` + --package-path $tempDir ` + --resources $resourcesDir ` + $finalPackagePath + } + + if (Test-Path $finalPackagePath) { + Write-Log "Successfully created macOS package: $finalPackagePath" + } + else { + throw "Package was not created at expected location: $finalPackagePath" + } + } + finally + { + Pop-Location + Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue + } } - return (Get-Item $newPackagePath) + return (Get-Item $finalPackagePath) } Class LinkInfo @@ -1424,7 +1596,7 @@ Class LinkInfo [string] $Destination } -function Get-FpmArguments +function New-RpmSpec { param( [Parameter(Mandatory,HelpMessage='Package Name')] @@ -1439,11 +1611,6 @@ function Get-FpmArguments [Parameter(Mandatory,HelpMessage='Package description')] [String]$Description, - # From start-PSPackage without modification, already validated - # Values: deb, rpm, osxpkg - [Parameter(Mandatory,HelpMessage='Installer Type')] - [String]$Type, - [Parameter(Mandatory,HelpMessage='Staging folder for installation files')] [String]$Staging, @@ -1459,109 +1626,505 @@ function Get-FpmArguments [Parameter(Mandatory,HelpMessage='Symlink to powershell executable')] [LinkInfo[]]$LinkInfo, - [Parameter(HelpMessage='Packages required to install this package. Not applicable for MacOS.')] - [ValidateScript({ - if (!$Environment.IsMacOS -and $_.Count -eq 0) - { - throw "Must not be null or empty on this environment." - } - return $true - })] + [Parameter(Mandatory,HelpMessage='Packages required to install this package')] [String[]]$Dependencies, - [Parameter(HelpMessage='Script to run after the package installation.')] - [AllowNull()] - [ValidateScript({ - if (!$Environment.IsMacOS -and !$_) - { - throw "Must not be null on this environment." - } - return $true - })] + [Parameter(Mandatory,HelpMessage='Script to run after the package installation.')] [String]$AfterInstallScript, - [Parameter(HelpMessage='Script to run after the package removal.')] - [AllowNull()] - [ValidateScript({ - if (!$Environment.IsMacOS -and !$_) - { - throw "Must not be null on this environment." - } - return $true - })] + [Parameter(Mandatory,HelpMessage='Script to run after the package removal.')] [String]$AfterRemoveScript, - [Parameter(HelpMessage='AppsFolder used to add macOS launcher')] - [AllowNull()] - [ValidateScript({ - if ($Environment.IsMacOS -and !$_) - { - throw "Must not be null on this environment." - } - return $true - })] - [String]$AppsFolder, [String]$Distribution = 'rhel.7', [string]$HostArchitecture ) - $Arguments = @( - "--force", "--verbose", - "--name", $Name, - "--version", $Version, - "--iteration", $Iteration, - "--maintainer", "PowerShell Team <PowerShellTeam@hotmail.com>", - "--vendor", "Microsoft Corporation", - "--url", "https://microsoft.com/powershell", - "--description", $Description, - "--architecture", $HostArchitecture, - "--category", "shells", - "-t", $Type, - "-s", "dir" - ) - if ($Distribution -in $script:RedHatDistributions) { - $Arguments += @("--rpm-digest", "sha256") - $Arguments += @("--rpm-dist", $Distribution) - $Arguments += @("--rpm-os", "linux") - $Arguments += @("--license", "MIT") - $Arguments += @("--rpm-rpmbuild-define", "_build_id_links none") + # RPM doesn't allow hyphens in version, so convert them to underscores + # e.g., "7.6.0-preview.6" becomes Version: 7.6.0_preview.6 + $rpmVersion = $Version -replace '-', '_' + + # Build Release field with distribution suffix (e.g., "1.cm" or "1.rh") + # Don't use RPM macros - build the full release string in PowerShell + $rpmRelease = "$Iteration.$Distribution" + + $specContent = @" +# RPM spec file for PowerShell +# Generated by PowerShell build system + +Name: $Name +Version: $rpmVersion +Release: $rpmRelease +Summary: PowerShell - Cross-platform automation and configuration tool/framework +License: MIT +URL: https://microsoft.com/powershell +AutoReq: no + +"@ + + # Only add BuildArch if not doing cross-architecture build + # For cross-arch builds, we'll rely on --target option + if ($HostArchitecture -eq "x86_64" -or $HostArchitecture -eq "noarch") { + $specContent += "BuildArch: $HostArchitecture`n`n" } else { - $Arguments += @("--license", "MIT License") - } + # For cross-architecture builds, don't specify BuildArch in spec + # The --target option will handle the architecture - if ($Environment.IsMacOS) { - $Arguments += @("--osxpkg-identifier-prefix", "com.microsoft") + # Disable automatic binary stripping for cross-arch builds + # The native /bin/strip on x86_64 cannot process ARM64 binaries and would fail with: + # "Unable to recognise the format of the input file" + # See: https://rpm-software-management.github.io/rpm/manual/macros.html + # __strip: This macro controls the command used for stripping binaries during the build process. + # /bin/true: A command that does nothing and always exits successfully, effectively bypassing the stripping process. + $specContent += "%define __strip /bin/true`n" + + # Disable debug package generation to prevent strip-related errors + # Debug packages require binary stripping which fails for cross-arch builds + # See: https://rpm-packaging-guide.github.io/#debugging + # See: https://docs.fedoraproject.org/en-US/packaging-guidelines/Debuginfo/#_useless_or_incomplete_debuginfo_packages_due_to_other_reasons + $specContent += "%global debug_package %{nil}`n`n" } - foreach ($Dependency in $Dependencies) { - $Arguments += @("--depends", $Dependency) + # Add dependencies + foreach ($dep in $Dependencies) { + $specContent += "Requires: $dep`n" } - if ($AfterInstallScript) { - $Arguments += @("--after-install", $AfterInstallScript) + $specContent += @" + +%description +$Description + +%prep +# No prep needed - files are already staged + +%build +# No build needed - binaries are pre-built + +%install +rm -rf `$RPM_BUILD_ROOT +mkdir -p `$RPM_BUILD_ROOT$Destination +mkdir -p `$RPM_BUILD_ROOT$(Split-Path -Parent $ManDestination) + +# Copy all files from staging to destination +cp -r $Staging/* `$RPM_BUILD_ROOT$Destination/ + +# Copy man page +cp $ManGzipFile `$RPM_BUILD_ROOT$ManDestination + +"@ + + # Add symlinks - we need to get the target of the temp symlink + foreach ($link in $LinkInfo) { + $linkDir = Split-Path -Parent $link.Destination + $specContent += "mkdir -p `$RPM_BUILD_ROOT$linkDir`n" + # For RPM, we copy the symlink itself. + # The symlink at $link.Source points to the actual target, so we'll copy it. + # The -P flag preserves symlinks rather than copying their targets, which is critical for this operation. + $specContent += "cp -P $($link.Source) `$RPM_BUILD_ROOT$($link.Destination)`n" } - if ($AfterRemoveScript) { - $Arguments += @("--after-remove", $AfterRemoveScript) + # Post-install script + $postInstallContent = Get-Content -Path $AfterInstallScript -Raw + $specContent += "`n%post`n" + $specContent += $postInstallContent + $specContent += "`n" + + # Post-uninstall script + $postUninstallContent = Get-Content -Path $AfterRemoveScript -Raw + $specContent += "%postun`n" + $specContent += $postUninstallContent + $specContent += "`n" + + # Files section + $specContent += "%files`n" + $specContent += "%defattr(-,root,root,-)`n" + $specContent += "$Destination/*`n" + $specContent += "$ManDestination`n" + + # Add symlinks to files + foreach ($link in $LinkInfo) { + $specContent += "$($link.Destination)`n" } - $Arguments += @( - "$Staging/=$Destination/", - "$ManGzipFile=$ManDestination" + # Changelog with correct date format for RPM + $changelogDate = Get-Date -Format "ddd MMM dd yyyy" + $specContent += "`n%changelog`n" + $specContent += "* $changelogDate PowerShell Team <PowerShellTeam@hotmail.com> - $rpmVersion-$rpmRelease`n" + $specContent += "- Automated build`n" + + return $specContent +} + +function New-NativeDeb +{ + param( + [Parameter(Mandatory, HelpMessage='Package Name')] + [String]$Name, + + [Parameter(Mandatory, HelpMessage='Package Version')] + [String]$Version, + + [Parameter(Mandatory)] + [String]$Iteration, + + [Parameter(Mandatory, HelpMessage='Package description')] + [String]$Description, + + [Parameter(Mandatory, HelpMessage='Staging folder for installation files')] + [String]$Staging, + + [Parameter(Mandatory, HelpMessage='Install path on target machine')] + [String]$Destination, + + [Parameter(Mandatory, HelpMessage='The built and gzipped man file.')] + [String]$ManGzipFile, + + [Parameter(Mandatory, HelpMessage='The destination of the man file')] + [String]$ManDestination, + + [Parameter(Mandatory, HelpMessage='Symlink to powershell executable')] + [LinkInfo[]]$LinkInfo, + + [Parameter(HelpMessage='Packages required to install this package.')] + [String[]]$Dependencies, + + [Parameter(HelpMessage='Script to run after the package installation.')] + [String]$AfterInstallScript, + + [Parameter(HelpMessage='Script to run after the package removal.')] + [String]$AfterRemoveScript, + + [string]$HostArchitecture, + + [string]$CurrentLocation ) - foreach($link in $LinkInfo) - { - $linkArgument = "$($link.Source)=$($link.Destination)" - $Arguments += $linkArgument - } + Write-Log "Creating native DEB package..." - if ($AppsFolder) - { - $Arguments += "$AppsFolder=/" + # Create temporary build directory + $debBuildRoot = Join-Path $env:HOME "debbuild-$(Get-Random)" + $debianDir = Join-Path $debBuildRoot "DEBIAN" + $dataDir = Join-Path $debBuildRoot "data" + + try { + New-Item -ItemType Directory -Path $debianDir -Force | Out-Null + New-Item -ItemType Directory -Path $dataDir -Force | Out-Null + + # Calculate installed size (in KB) + $installedSize = 0 + Get-ChildItem -Path $Staging -Recurse -File | ForEach-Object { $installedSize += $_.Length } + $installedSize += (Get-Item $ManGzipFile).Length + $installedSizeKB = [Math]::Ceiling($installedSize / 1024) + + # Create control file with all fields in proper order + # Description must be single line (first line) followed by extended description with leading space + $descriptionLines = $Description -split "`n" + $shortDescription = $descriptionLines[0] + $extendedDescription = if ($descriptionLines.Count -gt 1) { + ($descriptionLines[1..($descriptionLines.Count-1)] | ForEach-Object { " $_" }) -join "`n" + } + + $controlContent = @" +Package: $Name +Version: $Version-$Iteration +Architecture: $HostArchitecture +Maintainer: PowerShell Team <PowerShellTeam@hotmail.com> +Installed-Size: $installedSizeKB +Priority: optional +Section: shells +Homepage: https://microsoft.com/powershell +Depends: $(if ($Dependencies) { $Dependencies -join ', ' }) +Description: $shortDescription +$(if ($extendedDescription) { $extendedDescription + "`n" }) +"@ + + $controlFile = Join-Path $debianDir "control" + $controlContent | Out-File -FilePath $controlFile -Encoding ascii -NoNewline + + Write-Verbose "Control file created: $controlFile" -Verbose + Write-LogGroup -Title "DEB Control File Content" -Message $controlContent + + # Copy postinst script if provided + if ($AfterInstallScript -and (Test-Path $AfterInstallScript)) { + $postinstFile = Join-Path $debianDir "postinst" + Copy-Item -Path $AfterInstallScript -Destination $postinstFile -Force + Start-NativeExecution { chmod 755 $postinstFile } + Write-Verbose "Postinst script copied to: $postinstFile" -Verbose + } + + # Copy postrm script if provided + if ($AfterRemoveScript -and (Test-Path $AfterRemoveScript)) { + $postrmFile = Join-Path $debianDir "postrm" + Copy-Item -Path $AfterRemoveScript -Destination $postrmFile -Force + Start-NativeExecution { chmod 755 $postrmFile } + Write-Verbose "Postrm script copied to: $postrmFile" -Verbose + } + + # Copy staging files to data directory + $targetPath = Join-Path $dataDir $Destination.TrimStart('/') + New-Item -ItemType Directory -Path $targetPath -Force | Out-Null + Copy-Item -Path "$Staging/*" -Destination $targetPath -Recurse -Force + Write-Verbose "Copied staging files to: $targetPath" -Verbose + + # Copy man page + $manDestPath = Join-Path $dataDir $ManDestination.TrimStart('/') + $manDestDir = Split-Path $manDestPath -Parent + New-Item -ItemType Directory -Path $manDestDir -Force | Out-Null + Copy-Item -Path $ManGzipFile -Destination $manDestPath -Force + Write-Verbose "Copied man page to: $manDestPath" -Verbose + + # Copy symlinks from temporary locations + foreach ($link in $LinkInfo) { + $linkPath = Join-Path $dataDir $link.Destination.TrimStart('/') + $linkDir = Split-Path $linkPath -Parent + New-Item -ItemType Directory -Path $linkDir -Force | Out-Null + + # Copy the temporary symlink file that was created by New-LinkInfo + # The Source contains a temporary symlink that points to the correct target + if (Test-Path $link.Source) { + # Use cp to preserve the symlink + Start-NativeExecution { cp -P $link.Source $linkPath } + Write-Verbose "Copied symlink: $linkPath (from $($link.Source))" -Verbose + } else { + Write-Warning "Symlink source not found: $($link.Source)" + } + } + + # Set proper permissions + Write-Verbose "Setting file permissions..." -Verbose + # 755 = rwxr-xr-x (owner can read/write/execute, group and others can read/execute) + Get-ChildItem $dataDir -Directory -Recurse | ForEach-Object { + Start-NativeExecution { chmod 755 $_.FullName } + } + # 644 = rw-r--r-- (owner can read/write, group and others can read only) + # Exclude symlinks to avoid "cannot operate on dangling symlink" error + Get-ChildItem $dataDir -File -Recurse | + Where-Object { -not $_.Target } | + ForEach-Object { + Start-NativeExecution { chmod 644 $_.FullName } + } + + # Set executable permission for pwsh if it exists + # 755 = rwxr-xr-x (executable permission) + $pwshPath = "$targetPath/pwsh" + if (Test-Path $pwshPath) { + Start-NativeExecution { chmod 755 $pwshPath } + } + + # Included .NET executable for producing crash dumps + $createdumpPath = "$targetPath/createdump" + if (Test-Path $createdumpPath) { + Start-NativeExecution { chmod 755 $createdumpPath } + } + + # Calculate md5sums for all files in data directory (excluding symlinks) + $md5sumsFile = Join-Path $debianDir "md5sums" + $md5Content = "" + Get-ChildItem -Path $dataDir -Recurse -File | + Where-Object { -not $_.Target } | + ForEach-Object { + $relativePath = $_.FullName.Substring($dataDir.Length + 1) + $md5Hash = (Get-FileHash -Path $_.FullName -Algorithm MD5).Hash.ToLower() + $md5Content += "$md5Hash $relativePath`n" + } + $md5Content | Out-File -FilePath $md5sumsFile -Encoding ascii -NoNewline + Write-Verbose "MD5 sums file created: $md5sumsFile" -Verbose + + # Build the package using dpkg-deb + $debFileName = "${Name}_${Version}-${Iteration}_${HostArchitecture}.deb" + $debFilePath = Join-Path $CurrentLocation $debFileName + + Write-Verbose "Building DEB package: $debFileName" -Verbose + + # Copy DEBIAN directory and data files to build root + $buildDir = Join-Path $debBuildRoot "build" + New-Item -ItemType Directory -Path $buildDir -Force | Out-Null + + Write-Verbose "debianDir: $debianDir" -Verbose + Write-Verbose "dataDir: $dataDir" -Verbose + Write-Verbose "buildDir: $buildDir" -Verbose + + # Use cp to preserve symlinks + Start-NativeExecution { cp -a $debianDir "$buildDir/DEBIAN" } + Start-NativeExecution { cp -a $dataDir/* $buildDir } + + # Build package with dpkg-deb + Start-NativeExecution -VerboseOutputOnError { + dpkg-deb --build $buildDir $debFilePath + } + + if (Test-Path $debFilePath) { + Write-Log "Successfully created DEB package: $debFileName" + return @{ + PackagePath = $debFilePath + PackageName = $debFileName + } + } else { + throw "DEB package file not found after build: $debFilePath" + } } + finally { + # Cleanup temporary directory + if (Test-Path $debBuildRoot) { + Write-Verbose "Cleaning up temporary build directory: $debBuildRoot" -Verbose + Remove-Item -Path $debBuildRoot -Recurse -Force -ErrorAction SilentlyContinue + } + } +} - return $Arguments +function New-MacOSPackage +{ + [CmdletBinding(SupportsShouldProcess=$true)] + param( + [Parameter(Mandatory)] + [string]$Name, + + [Parameter(Mandatory)] + [string]$Version, + + [Parameter(Mandatory)] + [string]$Iteration, + + [Parameter(Mandatory)] + [string]$Staging, + + [Parameter(Mandatory)] + [string]$Destination, + + [Parameter(Mandatory)] + [string]$ManGzipFile, + + [Parameter(Mandatory)] + [string]$ManDestination, + + [Parameter(Mandatory)] + [LinkInfo[]]$LinkInfo, + + [Parameter(Mandatory)] + [string]$AfterInstallScript, + + [Parameter(Mandatory)] + [string]$AppsFolder, + + [Parameter(Mandatory)] + [string]$HostArchitecture, + + [string]$CurrentLocation = (Get-Location), + + [switch]$LTS + ) + + Write-Log "Creating macOS package using pkgbuild and productbuild..." + + # Create a temporary directory for package building + $tempRoot = New-TempFolder + $componentPkgPath = Join-Path $tempRoot "component.pkg" + $scriptsDir = Join-Path $tempRoot "scripts" + $resourcesDir = Join-Path $tempRoot "resources" + $distributionFile = Join-Path $tempRoot "distribution.xml" + + try { + # Create scripts directory + New-Item -ItemType Directory -Path $scriptsDir -Force | Out-Null + + # Copy and prepare the postinstall script + $postInstallPath = Join-Path $scriptsDir "postinstall" + Copy-Item -Path $AfterInstallScript -Destination $postInstallPath -Force + Start-NativeExecution { + chmod 755 $postInstallPath + } + + # Create a temporary directory for the package root + $pkgRoot = Join-Path $tempRoot "pkgroot" + New-Item -ItemType Directory -Path $pkgRoot -Force | Out-Null + + # Copy staging files to destination path in package root + $destInPkg = Join-Path $pkgRoot $Destination + New-Item -ItemType Directory -Path $destInPkg -Force | Out-Null + Write-Verbose "Copying staging files from $Staging to $destInPkg" -Verbose + Copy-Item -Path "$Staging/*" -Destination $destInPkg -Recurse -Force + + # Create man page directory structure + $manDir = Join-Path $pkgRoot (Split-Path $ManDestination -Parent) + New-Item -ItemType Directory -Path $manDir -Force | Out-Null + Copy-Item -Path $ManGzipFile -Destination (Join-Path $pkgRoot $ManDestination) -Force + + # Create symlinks in package root + # The LinkInfo contains Source (a temp file that IS a symlink) and Destination (where to install it) + foreach ($link in $LinkInfo) { + $linkDestDir = Join-Path $pkgRoot (Split-Path $link.Destination -Parent) + New-Item -ItemType Directory -Path $linkDestDir -Force | Out-Null + $finalLinkPath = Join-Path $pkgRoot $link.Destination + + Write-Verbose "Creating symlink at $finalLinkPath" -Verbose + + # Remove if exists + if (Test-Path $finalLinkPath) { + Remove-Item $finalLinkPath -Force + } + + # Get the target of the original symlink and recreate it in the package root + if (Test-Path $link.Source) { + $linkTarget = (Get-Item $link.Source).Target + if ($linkTarget) { + Write-Verbose "Creating symlink to target: $linkTarget" -Verbose + New-Item -ItemType SymbolicLink -Path $finalLinkPath -Target $linkTarget -Force | Out-Null + } else { + Write-Warning "Could not determine target for symlink at $($link.Source), copying file instead" + Copy-Item -Path $link.Source -Destination $finalLinkPath -Force + } + } else { + Write-Warning "Source symlink $($link.Source) does not exist" + } + } + + # Copy launcher app folder if provided + if ($AppsFolder) { + $appsInPkg = Join-Path $pkgRoot "Applications" + New-Item -ItemType Directory -Path $appsInPkg -Force | Out-Null + Write-Verbose "Copying launcher app from $AppsFolder to $appsInPkg" -Verbose + Copy-Item -Path "$AppsFolder/*" -Destination $appsInPkg -Recurse -Force + } + + # Get package identifier info based on version and LTS flag + $packageInfo = Get-MacOSPackageIdentifierInfo -Version $Version -LTS:$LTS + $IsPreview = $packageInfo.IsPreview + $pkgIdentifier = $packageInfo.PackageIdentifier + + if ($PSCmdlet.ShouldProcess("Build component package with pkgbuild")) { + Write-Log "Running pkgbuild to create component package..." + + Start-NativeExecution -VerboseOutputOnError { + pkgbuild --root $pkgRoot ` + --identifier $pkgIdentifier ` + --version $Version ` + --scripts $scriptsDir ` + --install-location "/" ` + $componentPkgPath + } + + Write-Verbose "Component package created: $componentPkgPath" -Verbose + } + + # Create the final distribution package using the refactored function + $distributionPackage = New-MacOsDistributionPackage ` + -ComponentPackage (Get-Item $componentPkgPath) ` + -PackageName $Name ` + -Version $Version ` + -OutputDirectory $CurrentLocation ` + -HostArchitecture $HostArchitecture ` + -PackageIdentifier $pkgIdentifier ` + -IsPreview:$IsPreview + + return $distributionPackage + } + finally { + # Clean up temporary directory + if (Test-Path $tempRoot) { + Write-Verbose "Cleaning up temporary directory: $tempRoot" -Verbose + Remove-Item -Path $tempRoot -Recurse -Force -ErrorAction SilentlyContinue + } + } } function Get-PackageDependencies @@ -1570,7 +2133,7 @@ function Get-PackageDependencies param() DynamicParam { # Add a dynamic parameter '-Distribution' when the specified package type is 'deb'. - # The '-Distribution' parameter can be used to indicate which Debian distro this pacakge is targeting. + # The '-Distribution' parameter can be used to indicate which Debian distro this package is targeting. $ParameterAttr = New-Object "System.Management.Automation.ParameterAttribute" $ParameterAttr.Mandatory = $true $ValidateSetAttr = New-Object "System.Management.Automation.ValidateSetAttribute" -ArgumentList $Script:AllDistributions @@ -1591,6 +2154,22 @@ function Get-PackageDependencies # These should match those in the Dockerfiles, but exclude tools like Git, which, and curl $Dependencies = @() + + # ICU version range follows .NET runtime policy. + # See: https://github.com/dotnet/runtime/blob/3fe8518d51bbcaa179bbe275b2597fbe1b88bc5a/src/native/libs/System.Globalization.Native/pal_icushim.c#L235-L243 + # + # Version range rationale: + # - The runtime supports ICU versions >= the version it was built against + # and <= that version + 30, to allow sufficient headroom for future releases. + # - ICU typically releases about twice per year, so +30 provides roughly + # 15 years of forward compatibility. + # - On some platforms, the minimum supported version may be lower + # than the build version and we know that older versions just works. + # + $MinICUVersion = 60 # runtime minimum supported + $BuildICUVersion = 76 # current build version + $MaxICUVersion = $BuildICUVersion + 30 # headroom + if ($Distribution -eq 'deb') { $Dependencies = @( "libc6", @@ -1598,10 +2177,9 @@ function Get-PackageDependencies "libgssapi-krb5-2", "libstdc++6", "zlib1g", - "libicu74|libicu72|libicu71|libicu70|libicu69|libicu68|libicu67|libicu66|libicu65|libicu63|libicu60|libicu57|libicu55|libicu52", + (($MaxICUVersion..$MinICUVersion).ForEach{ "libicu$_" } -join '|'), "libssl3|libssl1.1|libssl1.0.2|libssl1.0.0" ) - } elseif ($Distribution -eq 'rh') { $Dependencies = @( "openssl-libs", @@ -1636,25 +2214,25 @@ function Get-PackageDependencies function Test-Dependencies { - foreach ($Dependency in "fpm") { - if (!(precheck $Dependency "Package dependency '$Dependency' not found. Run Start-PSBootstrap -Scenario Package")) { - # These tools are not added to the path automatically on OpenSUSE 13.2 - # try adding them to the path and re-tesing first - [string] $gemsPath = $null - [string] $depenencyPath = $null - $gemsPath = Get-ChildItem -Path /usr/lib64/ruby/gems | Sort-Object -Property LastWriteTime -Descending | Select-Object -First 1 -ExpandProperty FullName - if ($gemsPath) { - $depenencyPath = Get-ChildItem -Path (Join-Path -Path $gemsPath -ChildPath "gems" -AdditionalChildPath $Dependency) -Recurse | Sort-Object -Property LastWriteTime -Descending | Select-Object -First 1 -ExpandProperty DirectoryName - $originalPath = $env:PATH - $env:PATH = $ENV:PATH +":" + $depenencyPath - if ((precheck $Dependency "Package dependency '$Dependency' not found. Run Start-PSBootstrap -Scenario Package")) { - continue - } - else { - $env:PATH = $originalPath - } - } + # RPM packages use rpmbuild directly. + # DEB packages use dpkg-deb directly. + # macOS packages use pkgbuild and productbuild from Xcode Command Line Tools. + $Dependencies = @() + # Check for 'rpmbuild' and 'dpkg-deb' on Azure Linux. + if ($Environment.IsMariner) { + $Dependencies += "dpkg-deb" + $Dependencies += "rpmbuild" + } + + # Check for macOS packaging tools + if ($Environment.IsMacOS) { + $Dependencies += "pkgbuild" + $Dependencies += "productbuild" + } + + foreach ($Dependency in $Dependencies) { + if (!(precheck $Dependency "Package dependency '$Dependency' not found. Run Start-PSBootstrap -Scenario Package")) { throw "Dependency precheck failed!" } } @@ -1743,20 +2321,44 @@ function New-ManGzip } } -# Returns the macOS Package Identifier -function Get-MacOSPackageId +<# + .SYNOPSIS + Determines the package identifier and preview status for macOS packages. + .DESCRIPTION + This function determines if a package is a preview build based on the version string + and LTS flag, then returns the appropriate package identifier. + .PARAMETER Version + The version string (e.g., "7.6.0-preview.6" or "7.6.0") + .PARAMETER LTS + Whether this is an LTS build + .OUTPUTS + Hashtable with IsPreview (boolean) and PackageIdentifier (string) properties + .EXAMPLE + Get-MacOSPackageIdentifierInfo -Version "7.6.0-preview.6" -LTS:$false + Returns @{ IsPreview = $true; PackageIdentifier = "com.microsoft.powershell-preview" } +#> +function Get-MacOSPackageIdentifierInfo { param( - [switch] - $IsPreview + [Parameter(Mandatory)] + [string]$Version, + + [switch]$LTS ) - if ($IsPreview.IsPresent) - { - return 'com.microsoft.powershell-preview' + + $IsPreview = Test-IsPreview -Version $Version -IsLTS:$LTS + + # Determine package identifier based on preview status + if ($IsPreview) { + $PackageIdentifier = 'com.microsoft.powershell-preview' } - else - { - return 'com.microsoft.powershell' + else { + $PackageIdentifier = 'com.microsoft.powershell' + } + + return @{ + IsPreview = $IsPreview + PackageIdentifier = $PackageIdentifier } } @@ -1770,8 +2372,9 @@ function New-MacOSLauncher [switch]$LTS ) - $IsPreview = Test-IsPreview -Version $Version -IsLTS:$LTS - $packageId = Get-MacOSPackageId -IsPreview:$IsPreview + $packageInfo = Get-MacOSPackageIdentifierInfo -Version $Version -LTS:$LTS + $IsPreview = $packageInfo.IsPreview + $packageId = $packageInfo.PackageIdentifier # Define folder for launcher application. $suffix = if ($IsPreview) { "-preview" } elseif ($LTS) { "-lts" } @@ -1809,10 +2412,12 @@ function New-MacOSLauncher # Set permissions for plist and shell script. Start-NativeExecution { chmod 644 $plist + } + Start-NativeExecution { chmod 755 $shellscript } - # Add app folder to fpm paths. + # Return the app folder path for packaging $appsfolder = (Resolve-Path -Path "$macosapp/..").Path return $appsfolder @@ -3179,443 +3784,6 @@ function Get-NugetSemanticVersion $packageSemanticVersion } -# Get the paths to various WiX tools -function Get-WixPath -{ - [CmdletBinding()] - param ( - [bool] $IsProductArchitectureArm = $false - ) - - $wixToolsetBinPath = $IsProductArchitectureArm ? "${env:ProgramFiles(x86)}\Arm Support WiX Toolset *\bin" : "${env:ProgramFiles(x86)}\WiX Toolset *\bin" - - Write-Verbose -Verbose "Ensure Wix Toolset is present on the machine @ $wixToolsetBinPath" - if (-not (Test-Path $wixToolsetBinPath)) - { - if (!$IsProductArchitectureArm) - { - throw "The latest version of Wix Toolset 3.11 is required to create MSI package. Please install it from https://github.com/wixtoolset/wix3/releases" - } - else { - throw "The latest version of Wix Toolset 3.14 is required to create MSI package for arm. Please install it from https://aka.ms/ps-wix-3-14-zip" - } - } - - ## Get the latest if multiple versions exist. - $wixToolsetBinPath = (Get-ChildItem $wixToolsetBinPath).FullName | Sort-Object -Descending | Select-Object -First 1 - - Write-Verbose "Initialize Wix executables..." - $wixHeatExePath = Join-Path $wixToolsetBinPath "heat.exe" - $wixMeltExePath = Join-Path $wixToolsetBinPath "melt.exe" - $wixTorchExePath = Join-Path $wixToolsetBinPath "torch.exe" - $wixPyroExePath = Join-Path $wixToolsetBinPath "pyro.exe" - $wixCandleExePath = Join-Path $wixToolsetBinPath "Candle.exe" - $wixLightExePath = Join-Path $wixToolsetBinPath "Light.exe" - $wixInsigniaExePath = Join-Path $wixToolsetBinPath "Insignia.exe" - - return [PSCustomObject] @{ - WixHeatExePath = $wixHeatExePath - WixMeltExePath = $wixMeltExePath - WixTorchExePath = $wixTorchExePath - WixPyroExePath = $wixPyroExePath - WixCandleExePath = $wixCandleExePath - WixLightExePath = $wixLightExePath - WixInsigniaExePath = $wixInsigniaExePath - } -} - -<# - .Synopsis - Creates a Windows installer MSI package and assumes that the binaries are already built using 'Start-PSBuild'. - This only works on a Windows machine due to the usage of WiX. - .EXAMPLE - # This example shows how to produce a Debug-x64 installer for development purposes. - cd $RootPathOfPowerShellRepo - Import-Module .\build.psm1; Import-Module .\tools\packaging\packaging.psm1 - New-MSIPackage -Verbose -ProductSourcePath '.\src\powershell-win-core\bin\Debug\net8.0\win7-x64\publish' -ProductTargetArchitecture x64 -ProductVersion '1.2.3' -#> -function New-MSIPackage -{ - [CmdletBinding()] - param ( - - # Name of the Product - [ValidateNotNullOrEmpty()] - [string] $ProductName = 'PowerShell', - - # Suffix of the Name - [string] $ProductNameSuffix, - - # Version of the Product - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string] $ProductVersion, - - # Source Path to the Product Files - required to package the contents into an MSI - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string] $ProductSourcePath, - - # File describing the MSI Package creation semantics - [ValidateNotNullOrEmpty()] - [ValidateScript( {Test-Path $_})] - [string] $ProductWxsPath = "$RepoRoot\assets\wix\Product.wxs", - - # File describing the MSI Package creation semantics - [ValidateNotNullOrEmpty()] - [ValidateScript({Test-Path $_})] - [string] $BundleWxsPath = "$RepoRoot\assets\wix\bundle.wxs", - - # Path to Assets folder containing artifacts such as icons, images - [ValidateNotNullOrEmpty()] - [ValidateScript( {Test-Path $_})] - [string] $AssetsPath = "$RepoRoot\assets", - - # Architecture to use when creating the MSI - [Parameter(Mandatory = $true)] - [ValidateSet("x86", "x64", "arm64")] - [ValidateNotNullOrEmpty()] - [string] $ProductTargetArchitecture, - - # Force overwrite of package - [Switch] $Force, - - [string] $CurrentLocation = (Get-Location) - ) - - $wixPaths = Get-WixPath -IsProductArchitectureArm ($ProductTargetArchitecture -eq "arm64") - - $windowsNames = Get-WindowsNames -ProductName $ProductName -ProductNameSuffix $ProductNameSuffix -ProductVersion $ProductVersion - $productSemanticVersionWithName = $windowsNames.ProductSemanticVersionWithName - $ProductSemanticVersion = $windowsNames.ProductSemanticVersion - $packageName = $windowsNames.PackageName - $ProductVersion = $windowsNames.ProductVersion - Write-Verbose "Create MSI for Product $productSemanticVersionWithName" -Verbose - Write-Verbose "ProductSemanticVersion = $productSemanticVersion" -Verbose - Write-Verbose "packageName = $packageName" -Verbose - Write-Verbose "ProductVersion = $ProductVersion" -Verbose - - $simpleProductVersion = [string]([Version]$ProductVersion).Major - $isPreview = Test-IsPreview -Version $ProductSemanticVersion - if ($isPreview) - { - $simpleProductVersion += '-preview' - } - - $staging = "$PSScriptRoot/staging" - New-StagingFolder -StagingPath $staging -PackageSourcePath $ProductSourcePath - - $assetsInSourcePath = Join-Path $staging 'assets' - - New-Item $assetsInSourcePath -type directory -Force | Write-Verbose - - Write-Verbose "Place dependencies such as icons to $assetsInSourcePath" - Copy-Item "$AssetsPath\*.ico" $assetsInSourcePath -Force - - - - $fileArchitecture = 'amd64' - $ProductProgFilesDir = "ProgramFiles64Folder" - if ($ProductTargetArchitecture -eq "x86") - { - $fileArchitecture = 'x86' - $ProductProgFilesDir = "ProgramFilesFolder" - } - elseif ($ProductTargetArchitecture -eq "arm64") - { - $fileArchitecture = 'arm64' - $ProductProgFilesDir = "ProgramFiles64Folder" - } - - $wixFragmentPath = Join-Path $env:Temp "Fragment.wxs" - - # cleanup any garbage on the system - Remove-Item -ErrorAction SilentlyContinue $wixFragmentPath -Force - - $msiLocationPath = Join-Path $CurrentLocation "$packageName.msi" - $msiPdbLocationPath = Join-Path $CurrentLocation "$packageName.wixpdb" - - if (!$Force.IsPresent -and (Test-Path -Path $msiLocationPath)) { - Write-Error -Message "Package already exists, use -Force to overwrite, path: $msiLocationPath" -ErrorAction Stop - } - - Write-Log "Generating wxs file manifest..." - $arguments = @{ - IsPreview = $isPreview - ProductSourcePath = $staging - ProductName = $ProductName - ProductVersion = $ProductVersion - SimpleProductVersion = $simpleProductVersion - ProductSemanticVersion = $ProductSemanticVersion - ProductVersionWithName = $productVersionWithName - ProductProgFilesDir = $ProductProgFilesDir - FileArchitecture = $fileArchitecture - } - - $buildArguments = New-MsiArgsArray -Argument $arguments - - Test-Bom -Path $staging -BomName windows -Architecture $ProductTargetArchitecture -Verbose - Start-NativeExecution -VerboseOutputOnError { & $wixPaths.wixHeatExePath dir $staging -dr VersionFolder -cg ApplicationFiles -ag -sfrag -srd -scom -sreg -out $wixFragmentPath -var var.ProductSourcePath $buildArguments -v} - - Send-AzdoFile -Path $wixFragmentPath - - $wixObjFragmentPath = Join-Path $env:Temp "Fragment.wixobj" - - # cleanup any garbage on the system - Remove-Item -ErrorAction SilentlyContinue $wixObjFragmentPath -Force - - Start-MsiBuild -WxsFile $ProductWxsPath, $wixFragmentPath -ProductTargetArchitecture $ProductTargetArchitecture -Argument $arguments -MsiLocationPath $msiLocationPath -MsiPdbLocationPath $msiPdbLocationPath - - Remove-Item -ErrorAction SilentlyContinue $wixFragmentPath -Force - - if ((Test-Path $msiLocationPath) -and (Test-Path $msiPdbLocationPath)) - { - Write-Verbose "You can find the WixPdb @ $msiPdbLocationPath" -Verbose - Write-Verbose "You can find the MSI @ $msiLocationPath" -Verbose - [pscustomobject]@{ - msi=$msiLocationPath - wixpdb=$msiPdbLocationPath - } - } - else - { - $errorMessage = "Failed to create $msiLocationPath" - throw $errorMessage - } -} - -function Get-WindowsNames { - param( - # Name of the Product - [ValidateNotNullOrEmpty()] - [string] $ProductName = 'PowerShell', - - # Suffix of the Name - [string] $ProductNameSuffix, - - # Version of the Product - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string] $ProductVersion - ) - - Write-Verbose -Message "Getting Windows Names for ProductName: $ProductName; ProductNameSuffix: $ProductNameSuffix; ProductVersion: $ProductVersion" -Verbose - - $ProductSemanticVersion = Get-PackageSemanticVersion -Version $ProductVersion - $ProductVersion = Get-PackageVersionAsMajorMinorBuildRevision -Version $ProductVersion -IncrementBuildNumber - - $productVersionWithName = $ProductName + '_' + $ProductVersion - $productSemanticVersionWithName = $ProductName + '-' + $ProductSemanticVersion - - $packageName = $productSemanticVersionWithName - if ($ProductNameSuffix) { - $packageName += "-$ProductNameSuffix" - } - - return [PSCustomObject]@{ - PackageName = $packageName - ProductVersionWithName = $productVersionWithName - ProductSemanticVersion = $ProductSemanticVersion - ProductSemanticVersionWithName = $productSemanticVersionWithName - ProductVersion = $ProductVersion - } -} - -function New-ExePackage { - param( - # Name of the Product - [ValidateNotNullOrEmpty()] - [string] $ProductName = 'PowerShell', - - # Version of the Product - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - - [string] $ProductVersion, - - # File describing the MSI Package creation semantics - [ValidateNotNullOrEmpty()] - [ValidateScript({Test-Path $_})] - [string] $BundleWxsPath = "$RepoRoot\assets\wix\bundle.wxs", - - # Architecture to use when creating the MSI - [Parameter(Mandatory = $true)] - [ValidateSet("x86", "x64", "arm64")] - [ValidateNotNullOrEmpty()] - [string] $ProductTargetArchitecture, - - # Location of the signed MSI - [Parameter(Mandatory = $true)] - [string] - $MsiLocationPath, - - [string] $CurrentLocation = (Get-Location) - ) - - $productNameSuffix = "win-$ProductTargetArchitecture" - - $windowsNames = Get-WindowsNames -ProductName $ProductName -ProductNameSuffix $productNameSuffix -ProductVersion $ProductVersion - $productSemanticVersionWithName = $windowsNames.ProductSemanticVersionWithName - $packageName = $windowsNames.PackageName - $isPreview = Test-IsPreview -Version $windowsNames.ProductSemanticVersion - - Write-Verbose "Create EXE for Product $productSemanticVersionWithName" -verbose - Write-Verbose "packageName = $packageName" -Verbose - - $exeLocationPath = Join-Path $CurrentLocation "$packageName.exe" - $exePdbLocationPath = Join-Path $CurrentLocation "$packageName.exe.wixpdb" - $windowsVersion = Get-WindowsVersion -packageName $packageName - - Start-MsiBuild -WxsFile $BundleWxsPath -ProductTargetArchitecture $ProductTargetArchitecture -Argument @{ - IsPreview = $isPreview - TargetPath = $MsiLocationPath - WindowsVersion = $windowsVersion - } -MsiLocationPath $exeLocationPath -MsiPdbLocationPath $exePdbLocationPath - - return $exeLocationPath -} - -<# -Allows you to extract the engine of exe package, mainly for signing -Any existing signature will be removed. - #> -function Expand-ExePackageEngine { - param( - # Location of the unsigned EXE - [Parameter(Mandatory = $true)] - [string] - $ExePath, - - # Location to put the expanded engine. - [Parameter(Mandatory = $true)] - [string] - $EnginePath, - - [Parameter(Mandatory = $true)] - [ValidateSet("x86", "x64", "arm64")] - [ValidateNotNullOrEmpty()] - [string] $ProductTargetArchitecture - ) - - <# - 2. detach the engine from TestInstaller.exe: - insignia -ib TestInstaller.exe -o engine.exe - #> - - $wixPaths = Get-WixPath -IsProductArchitectureArm ($ProductTargetArchitecture -eq "arm64") - - $resolvedExePath = (Resolve-Path -Path $ExePath).ProviderPath - $resolvedEnginePath = [System.IO.Path]::GetFullPath($EnginePath) - - Start-NativeExecution -VerboseOutputOnError { & $wixPaths.wixInsigniaExePath -ib $resolvedExePath -o $resolvedEnginePath} -} - -<# -Allows you to replace the engine (installer) in the exe package. -Used to replace the engine with a signed version -#> -function Compress-ExePackageEngine { - param( - # Location of the unsigned EXE - [Parameter(Mandatory = $true)] - [string] - $ExePath, - - # Location of the signed engine - [Parameter(Mandatory = $true)] - [string] - $EnginePath, - - [Parameter(Mandatory = $true)] - [ValidateSet("x86", "x64", "arm64")] - [ValidateNotNullOrEmpty()] - [string] $ProductTargetArchitecture - ) - - - <# - 4. re-attach the signed engine.exe to the bundle: - insignia -ab engine.exe TestInstaller.exe -o TestInstaller.exe - #> - - $wixPaths = Get-WixPath -IsProductArchitectureArm ($ProductTargetArchitecture -eq "arm64") - - $resolvedEnginePath = (Resolve-Path -Path $EnginePath).ProviderPath - $resolvedExePath = (Resolve-Path -Path $ExePath).ProviderPath - - Start-NativeExecution -VerboseOutputOnError { & $wixPaths.wixInsigniaExePath -ab $resolvedEnginePath $resolvedExePath -o $resolvedExePath} -} - -function New-MsiArgsArray { - param( - [Parameter(Mandatory)] - [Hashtable]$Argument - ) - - $buildArguments = @() - foreach ($key in $Argument.Keys) { - $buildArguments += "-d$key=$($Argument.$key)" - } - - return $buildArguments -} - -function Start-MsiBuild { - param( - [string[]] $WxsFile, - [string[]] $Extension = @('WixUIExtension', 'WixUtilExtension', 'WixBalExtension'), - [string] $ProductTargetArchitecture, - [Hashtable] $Argument, - [string] $MsiLocationPath, - [string] $MsiPdbLocationPath - ) - - $outDir = $env:Temp - - $wixPaths = Get-WixPath -IsProductArchitectureArm ($ProductTargetArchitecture -eq "arm64") - - $extensionArgs = @() - foreach ($extensionName in $Extension) { - $extensionArgs += '-ext' - $extensionArgs += $extensionName - } - - $buildArguments = New-MsiArgsArray -Argument $Argument - - $objectPaths = @() - foreach ($file in $WxsFile) { - $fileName = [system.io.path]::GetFileNameWithoutExtension($file) - $objectPaths += Join-Path $outDir -ChildPath "${filename}.wixobj" - } - - foreach ($file in $objectPaths) { - Remove-Item -ErrorAction SilentlyContinue $file -Force - Remove-Item -ErrorAction SilentlyContinue $file -Force - } - - $resolvedWxsFiles = @() - foreach ($file in $WxsFile) { - $resolvedWxsFiles += (Resolve-Path -Path $file).ProviderPath - } - - Write-Verbose "$resolvedWxsFiles" -Verbose - - Write-Log "running candle..." - Start-NativeExecution -VerboseOutputOnError { & $wixPaths.wixCandleExePath $resolvedWxsFiles -out "$outDir\\" $extensionArgs -arch $ProductTargetArchitecture $buildArguments -v} - - Write-Log "running light..." - # suppress ICE61, because we allow same version upgrades - # suppress ICE57, this suppresses an error caused by our shortcut not being installed per user - # suppress ICE40, REINSTALLMODE is defined in the Property table. - Start-NativeExecution -VerboseOutputOnError {& $wixPaths.wixLightExePath -sice:ICE61 -sice:ICE40 -sice:ICE57 -out $msiLocationPath -pdbout $msiPdbLocationPath $objectPaths $extensionArgs } - - foreach($file in $objectPaths) - { - Remove-Item -ErrorAction SilentlyContinue $file -Force - Remove-Item -ErrorAction SilentlyContinue $file -Force - } -} - <# .Synopsis Creates a Windows AppX MSIX package and assumes that the binaries are already built using 'Start-PSBuild'. @@ -3656,6 +3824,9 @@ function New-MSIXPackage # Produce private package for testing in Store [Switch] $Private, + # Produce LTS package + [Switch] $LTS, + # Force overwrite of package [Switch] $Force, @@ -3681,18 +3852,8 @@ function New-MSIXPackage $makepri = Get-Item (Join-Path $makeappx.Directory "makepri.exe") -ErrorAction Stop + $displayName = $ProductName $ProductSemanticVersion = Get-PackageSemanticVersion -Version $ProductVersion - $productSemanticVersionWithName = $ProductName + '-' + $ProductSemanticVersion - $packageName = $productSemanticVersionWithName - if ($Private) { - $ProductNameSuffix = 'Private' - } - - if ($ProductNameSuffix) { - $packageName += "-$ProductNameSuffix" - } - - $displayName = $productName if ($Private) { $ProductName = 'PowerShell-Private' @@ -3700,11 +3861,21 @@ function New-MSIXPackage } elseif ($ProductSemanticVersion.Contains('-')) { $ProductName += 'Preview' $displayName += ' Preview' + } elseif ($LTS) { + $ProductName += '-LTS' + $displayName += ' LTS' } Write-Verbose -Verbose "ProductName: $productName" Write-Verbose -Verbose "DisplayName: $displayName" + $packageName = $ProductName + '-' + $ProductSemanticVersion + + # Appends Architecture to the package name + if ($ProductNameSuffix) { + $packageName += "-$ProductNameSuffix" + } + $ProductVersion = Get-WindowsVersion -PackageName $packageName # Any app that is submitted to the Store must have a PhoneIdentity in its appxmanifest. @@ -3719,10 +3890,13 @@ function New-MSIXPackage # This is the PhoneProductId for the "Microsoft.PowerShellPreview" package. $PhoneProductId = "67859fd2-b02a-45be-8fb5-62c569a3e8bf" Write-Verbose "Using Preview assets" -Verbose + } elseif ($LTS) { + # This is the PhoneProductId for the "Microsoft.PowerShell-LTS" package. + $PhoneProductId = "b7a4b003-3704-47a9-b018-cfcc9801f4fc" + Write-Verbose "Using LTS assets" -Verbose } - # Appx manifest needs to be in root of source path, but the embedded version needs to be updated - # cp-459155 is 'CN=Microsoft Windows Store Publisher (Store EKU), O=Microsoft Corporation, L=Redmond, S=Washington, C=US' + # Appx manifest needs to be in root of source path, but the embedded version needs to be updated. # authenticodeFormer is 'CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US' $releasePublisher = 'CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US' @@ -3764,7 +3938,6 @@ function New-MSIXPackage else { Copy-Item -Path "$RepoRoot\assets\$_.png" -Destination "$ProductSourcePath\assets\" } - } if ($PSCmdlet.ShouldProcess("Create .msix package?")) { @@ -3777,6 +3950,7 @@ function New-MSIXPackage Write-Verbose "Creating msix package" -Verbose Start-NativeExecution -VerboseOutputOnError { & $makeappx pack /o /v /h SHA256 /d $ProductSourcePath /p (Join-Path -Path $CurrentLocation -ChildPath "$packageName.msix") } Write-Verbose "Created $packageName.msix" -Verbose + Join-Path -Path $CurrentLocation -ChildPath "$packageName.msix" } } @@ -4320,7 +4494,7 @@ function New-GlobalToolNupkgSource } # Set VSTS environment variable for CGManifest file path. - $globalToolCGManifestPFilePath = Join-Path -Path "$env:REPOROOT" -ChildPath "tools\cgmanifest.json" + $globalToolCGManifestPFilePath = Join-Path -Path "$env:REPOROOT" -ChildPath "tools/cgmanifest/main/cgmanifest.json" $globalToolCGManifestFilePath = Resolve-Path -Path $globalToolCGManifestPFilePath -ErrorAction SilentlyContinue if (($null -eq $globalToolCGManifestFilePath) -or (! (Test-Path -Path $globalToolCGManifestFilePath))) { @@ -4984,8 +5158,24 @@ function Send-AzdoFile { Copy-Item -Path $Path -Destination $logFile } - Write-Host "##vso[artifact.upload containerfolder=$newName;artifactname=$newName]$logFile" - Write-Verbose "Log file captured as $newName" -Verbose + Write-Verbose "Capture the log file as '$newName'" -Verbose + if($env:TF_BUILD) { + ## In Azure DevOps + Write-Host "##vso[artifact.upload containerfolder=$newName;artifactname=$newName]$logFile" + } elseif ($env:GITHUB_WORKFLOW -and $env:SYSTEM_ARTIFACTSDIRECTORY) { + ## In GitHub Actions + $destinationPath = $env:SYSTEM_ARTIFACTSDIRECTORY + Write-Verbose "Upload '$logFile' to '$destinationPath' in GitHub Action" -Verbose + + # Create the folder if it does not exist + if (!(Test-Path -Path $destinationPath)) { + $null = New-Item -ItemType Directory -Path $destinationPath -Force + } + + Copy-Item -Path $logFile -Destination $destinationPath -Force -Verbose + } else { + Write-Warning "This environment is neither Azure Devops nor GitHub Actions. Cannot capture the log file in this environment." + } } # Class used for serializing and deserialing a BOM into Json diff --git a/tools/packaging/packaging.strings.psd1 b/tools/packaging/packaging.strings.psd1 index eeb9a86ec10..0bf14ff0dbe 100644 --- a/tools/packaging/packaging.strings.psd1 +++ b/tools/packaging/packaging.strings.psd1 @@ -166,7 +166,7 @@ open {0} <files include="**/*" buildAction="None" copyToOutput="true" flatten="false" /> </contentFiles> <dependencies> - <group targetFramework="net10.0"></group> + <group targetFramework="net11.0"></group> </dependencies> </metadata> </package> diff --git a/tools/packaging/projects/reference/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj b/tools/packaging/projects/reference/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj index f358b454baa..34ead3a2dc5 100644 --- a/tools/packaging/projects/reference/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj +++ b/tools/packaging/projects/reference/Microsoft.PowerShell.Commands.Utility/Microsoft.PowerShell.Commands.Utility.csproj @@ -1,11 +1,11 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> - <TargetFramework>net10.0</TargetFramework> + <TargetFramework>net11.0</TargetFramework> <Version>$(RefAsmVersion)</Version> <DelaySign>true</DelaySign> <AssemblyOriginatorKeyFile>$(SnkFile)</AssemblyOriginatorKeyFile> <SignAssembly>true</SignAssembly> - <LangVersion>13.0</LangVersion> + <LangVersion>preview</LangVersion> </PropertyGroup> <ItemGroup> <Reference Include="System.Management.Automation"> diff --git a/tools/packaging/projects/reference/Microsoft.PowerShell.ConsoleHost/Microsoft.PowerShell.ConsoleHost.csproj b/tools/packaging/projects/reference/Microsoft.PowerShell.ConsoleHost/Microsoft.PowerShell.ConsoleHost.csproj index c0794a6a708..378ca59ff95 100644 --- a/tools/packaging/projects/reference/Microsoft.PowerShell.ConsoleHost/Microsoft.PowerShell.ConsoleHost.csproj +++ b/tools/packaging/projects/reference/Microsoft.PowerShell.ConsoleHost/Microsoft.PowerShell.ConsoleHost.csproj @@ -1,11 +1,11 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> - <TargetFramework>net10.0</TargetFramework> + <TargetFramework>net11.0</TargetFramework> <Version>$(RefAsmVersion)</Version> <DelaySign>true</DelaySign> <AssemblyOriginatorKeyFile>$(SnkFile)</AssemblyOriginatorKeyFile> <SignAssembly>true</SignAssembly> - <LangVersion>13.0</LangVersion> + <LangVersion>preview</LangVersion> </PropertyGroup> <ItemGroup> <Reference Include="System.Management.Automation"> diff --git a/tools/packaging/projects/reference/System.Management.Automation/System.Management.Automation.csproj b/tools/packaging/projects/reference/System.Management.Automation/System.Management.Automation.csproj index f4626e110fd..8f80aa50a3d 100644 --- a/tools/packaging/projects/reference/System.Management.Automation/System.Management.Automation.csproj +++ b/tools/packaging/projects/reference/System.Management.Automation/System.Management.Automation.csproj @@ -1,11 +1,11 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> - <TargetFramework>net10.0</TargetFramework> + <TargetFramework>net11.0</TargetFramework> <Version>$(RefAsmVersion)</Version> <DelaySign>true</DelaySign> <AssemblyOriginatorKeyFile>$(SnkFile)</AssemblyOriginatorKeyFile> <SignAssembly>true</SignAssembly> - <LangVersion>13.0</LangVersion> + <LangVersion>preview</LangVersion> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.Management.Infrastructure" Version="3.0.0" /> diff --git a/tools/releaseToWinget.ps1 b/tools/releaseToWinget.ps1 deleted file mode 100644 index e3c1af080e3..00000000000 --- a/tools/releaseToWinget.ps1 +++ /dev/null @@ -1,166 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -param( - [Parameter(Mandatory)] - [semver] - $ReleaseVersion, - - [Parameter()] - [string] - $WingetRepoPath = "$PSScriptRoot/../../winget-pkgs", - - [Parameter()] - [string] - $FromRepository = 'rjmholt', - - [Parameter()] - [string] - $GitHubToken -) - -function GetMsiHash -{ - param( - [Parameter(Mandatory)] - [string] - $ReleaseVersion, - - [Parameter(Mandatory)] - $MsiName - ) - - $releaseParams = @{ - Tag = "v$ReleaseVersion" - OwnerName = 'PowerShell' - RepositoryName = 'PowerShell' - } - - if ($GitHubToken) { $releaseParams.AccessToken = $GitHubToken } - - $releaseDescription = (Get-GitHubRelease @releaseParams).body - - $regex = [regex]::new("powershell-$ReleaseVersion-win-x64.msi.*?([0-9A-F]{64})", 'SingleLine,IgnoreCase') - - return $regex.Match($releaseDescription).Groups[1].Value -} - -function GetThisScriptRepoUrl -{ - # Find the root of the repo - $prefix = $PSScriptRoot - while ($prefix) - { - if (Test-Path "$prefix/LICENSE.txt") - { - break - } - - $prefix = Split-Path $prefix - } - - $stem = $PSCommandPath.Substring($prefix.Length + 1).Replace('\', '/') - - return "https://github.com/PowerShell/PowerShell/blob/master/$stem" -} - -function Exec -{ - param([scriptblock]$sb) - - & $sb - - if ($LASTEXITCODE -ne 0) - { - throw "Invocation failed for '$sb'. See above errors for details" - } -} - -$ErrorActionPreference = 'Stop' - -$wingetPath = (Resolve-Path $WingetRepoPath).Path - -# Ensure we have git and PowerShellForGitHub installed -Import-Module -Name PowerShellForGitHub -$null = Get-Command git - -# Get the MSI hash from the release body -$msiName = "PowerShell-$ReleaseVersion-win-x64.msi" -$msiHash = GetMsiHash -ReleaseVersion $ReleaseVersion -MsiName $msiName - -$publisherName = 'Microsoft' - -# Create the manifest -$productName = if ($ReleaseVersion.PreReleaseLabel) -{ - 'PowerShell-Preview' -} -else -{ - 'PowerShell' -} - -$manifestDir = Join-Path $wingetPath 'manifests' 'm' $publisherName $productName $ReleaseVersion -$manifestPath = Join-Path $manifestDir "$publisherName.$productName.yaml" - -$manifestContent = @" -PackageIdentifier: $publisherName.$productName -PackageVersion: $ReleaseVersion -PackageName: $productName -Publisher: $publisherName -PackageUrl: https://microsoft.com/PowerShell -License: MIT -LicenseUrl: https://github.com/PowerShell/PowerShell/blob/master/LICENSE.txt -Moniker: $($productName.ToLower()) -ShortDescription: $publisherName.$productName -Description: PowerShell is a cross-platform (Windows, Linux, and macOS) automation and configuration tool/framework that works well with your existing tools and is optimized for dealing with structured data (e.g. JSON, CSV, XML, etc.), REST APIs, and object models. It includes a command-line shell, an associated scripting language and a framework for processing cmdlets. -Tags: -- powershell -- pwsh -Homepage: https://github.com/PowerShell/PowerShell -Installers: -- Architecture: x64 - InstallerUrl: https://github.com/PowerShell/PowerShell/releases/download/v$ReleaseVersion/$msiName - InstallerSha256: $msiHash - InstallerType: msi -PackageLocale: en-US -ManifestType: singleton -ManifestVersion: 1.0.0 - -"@ - -Push-Location $wingetPath -try -{ - $branch = "pwsh-$ReleaseVersion" - - Exec { git checkout master } - Exec { git checkout -b $branch } - - New-Item -Path $manifestDir -ItemType Directory - Set-Content -Path $manifestPath -Value $manifestContent -Encoding utf8NoBOM - - Exec { git add $manifestPath } - Exec { git commit -m "Add $productName $ReleaseVersion" } - Exec { git push origin $branch } - - $prParams = @{ - Title = "Add $productName $ReleaseVersion" - Body = "This pull request is automatically generated. See $(GetThisScriptRepoUrl)." - Head = $branch - HeadOwner = $FromRepository - Base = 'master' - Owner = 'Microsoft' - RepositoryName = 'winget-pkgs' - MaintainerCanModify = $true - } - - if ($GitHubToken) { $prParams.AccessToken = $GitHubToken } - - New-GitHubPullRequest @prParams -} -finally -{ - git checkout master - Pop-Location -} diff --git a/tools/releaseTools.psm1 b/tools/releaseTools.psm1 index b034e2863dd..99edf93c08a 100644 --- a/tools/releaseTools.psm1 +++ b/tools/releaseTools.psm1 @@ -37,6 +37,7 @@ $Script:powershell_team = @( "dependabot-preview[bot]" "dependabot[bot]" "github-actions[bot]" + "Copilot" "Anam Navied" "Andrew Schwartzmeyer" "Jason Helmick" @@ -46,6 +47,27 @@ $Script:powershell_team = @( "Justin Chung" ) +# The powershell team members GitHub logins. We use them to decide if the original author of a backport PR is from the team. +$script:psteam_logins = @( + 'andyleejordan' + 'TravisEz13' + 'daxian-dbw' + 'adityapatwardhan' + 'SteveL-MSFT' + 'dependabot[bot]' + 'pwshBot' + 'jshigetomi' + 'SeeminglyScience' + 'anamnavi' + 'sdwheeler' + 'Copilot' + 'copilot-swe-agent' + 'app/copilot-swe-agent' + 'StevenBucher98' + 'alerickson' + 'tgauth' +) + # They are very active contributors, so we keep their email-login mappings here to save a few queries to Github. $Script:community_login_map = @{ "darpa@yandex.ru" = "iSazonov" @@ -54,11 +76,6 @@ $Script:community_login_map = @{ "info@powercode-consulting.se" = "powercode" } -# Ignore dependency bumping bot (Dependabot): -$Script:attribution_ignore_list = @( - 'dependabot[bot]@users.noreply.github.com' -) - ############################## #.SYNOPSIS #In the release workflow, the release branch will be merged back to master after the release is done, @@ -262,25 +279,76 @@ function Get-ChangeLog $clExperimental = @() foreach ($commit in $new_commits) { + $commitSubject = $commit.Subject + $prNumber = $commit.PullRequest + Write-Verbose "subject: $commitSubject" Write-Verbose "authorname: $($commit.AuthorName)" - if ($commit.AuthorEmail.EndsWith("@microsoft.com") -or $powershell_team -contains $commit.AuthorName -or $Script:attribution_ignore_list -contains $commit.AuthorEmail) { - $commit.ChangeLogMessage = "- {0}" -f (Get-ChangeLogMessage $commit.Subject) + + try { + $pr = Invoke-RestMethod ` + -Uri "https://api.github.com/repos/PowerShell/PowerShell/pulls/$prNumber" ` + -Headers $header ` + -ErrorAction Stop ` + -Verbose:$false ## Always disable verbose to avoid noise when we debug this function. + } catch { + ## A commit may not have corresponding GitHub PRs. In that case, we will get status code 404 (Not Found). + ## Otherwise, let the error bubble up. + if ($_.Exception.Response.StatusCode -ne 404) { + throw + } + } + + if ($commitSubject -match '^\[release/v\d\.\d\] ') { + ## The commit was from a backport PR. We need to get the real author in this case. + if (-not $pr) { + throw "The commit is from a backport PR (#$prNumber), but the PR cannot be found.`nPR Title: $commitSubject" + } + + $userPattern = 'Triggered by @.+ on behalf of @(.+)' + if ($pr.body -match $userPattern) { + $commit.AuthorGitHubLogin = ($Matches.1).Trim() + Write-Verbose "backport PR. real author login: $($commit.AuthorGitHubLogin)" + } else { + throw "The commit is from a backport PR (#$prNumber), but the PR description failed to match the pattern '$userPattern'. Was the template for backport PRs changed?`nPR Title: $commitSubject" + } + } + + if ($commit.AuthorGitHubLogin) { + if ($script:psteam_logins -contains $commit.AuthorGitHubLogin) { + $commit.ChangeLogMessage = "- {0}" -f (Get-ChangeLogMessage $commitSubject) + } else { + $commit.ChangeLogMessage = ("- {0} (Thanks @{1}!)" -f (Get-ChangeLogMessage $commitSubject), $commit.AuthorGitHubLogin) + $commit.ThankYouMessage = ("@{0}" -f ($commit.AuthorGitHubLogin)) + } + } elseif ($commit.AuthorEmail.EndsWith("@microsoft.com") -or $powershell_team -contains $commit.AuthorName) { + $commit.ChangeLogMessage = "- {0}" -f (Get-ChangeLogMessage $commitSubject) } else { if ($community_login_map.ContainsKey($commit.AuthorEmail)) { $commit.AuthorGitHubLogin = $community_login_map[$commit.AuthorEmail] } else { - $uri = "https://api.github.com/repos/PowerShell/PowerShell/commits/$($commit.Hash)" try{ - $response = Invoke-WebRequest -Uri $uri -Method Get -Headers $header -ErrorAction Ignore - } catch{} + ## Always disable verbose to avoid noise when we debug this function. + $response = Invoke-RestMethod ` + -Uri "https://api.github.com/repos/PowerShell/PowerShell/commits/$($commit.Hash)" ` + -Headers $header ` + -ErrorAction Stop ` + -Verbose:$false + } catch { + ## A commit could be available in ADO only. In that case, we will get status code 422 (UnprocessableEntity). + ## Otherwise, let the error bubble up. + if ($_.Exception.Response.StatusCode -ne 422) { + throw + } + } + if($response) { - $content = ConvertFrom-Json -InputObject $response.Content - $commit.AuthorGitHubLogin = $content.author.login + $commit.AuthorGitHubLogin = $response.author.login $community_login_map[$commit.AuthorEmail] = $commit.AuthorGitHubLogin } } - $commit.ChangeLogMessage = ("- {0} (Thanks @{1}!)" -f (Get-ChangeLogMessage $commit.Subject), $commit.AuthorGitHubLogin) + + $commit.ChangeLogMessage = ("- {0} (Thanks @{1}!)" -f (Get-ChangeLogMessage $commitSubject), $commit.AuthorGitHubLogin) $commit.ThankYouMessage = ("@{0}" -f ($commit.AuthorGitHubLogin)) } @@ -289,16 +357,6 @@ function Get-ChangeLog } ## Get the labels for the PR - try { - $pr = Invoke-RestMethod -Uri "https://api.github.com/repos/PowerShell/PowerShell/pulls/$($commit.PullRequest)" -Headers $header -ErrorAction SilentlyContinue - } - catch { - if ($_.Exception.Response.StatusCode -eq '404') { - $pr = $null - #continue - } - } - if($pr) { $clLabel = $pr.labels | Where-Object { $_.Name -match "^CL-"} @@ -328,7 +386,7 @@ function Get-ChangeLog "CL-Tools" { $clTools += $commit } "CL-Untagged" { $clUntagged += $commit } "CL-NotInBuild" { continue } - Default { throw "unknown tag '$cLabel' for PR: '$($commit.PullRequest)'" } + Default { throw "unknown tag '$cLabel' for PR: '$prNumber'" } } } } @@ -426,6 +484,9 @@ function Get-ChangeLogMessage '^Build\(deps\): ' { return $OriginalMessage.replace($Matches.0,'') } + '^\[release/v\d\.\d\] ' { + return $OriginalMessage.replace($Matches.0,'') + } default { return $OriginalMessage } @@ -867,8 +928,8 @@ function Invoke-PRBackport { ) $continue = $false while(!$continue) { - $input= Read-Host -Prompt ($Message + "`nType 'Yes<enter>' to continue 'No<enter>' to exit") - switch($input) { + $value = Read-Host -Prompt ($Message + "`nType 'Yes<enter>' to continue 'No<enter>' to exit") + switch($value) { 'yes' { $continue= $true }