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 d4adcefefad..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 @@ -10,8 +13,8 @@ src/System.Management.Automation/security/wldpNativeMethods.cs @TravisEz13 @seeminglyscience # Area: CI Build -.github/workflows @PowerShell/powershell-maintainers -.github/actions @PowerShell/powershell-maintainers +.github/workflows @PowerShell/powershell-maintainers @jshigetomi +.github/actions @PowerShell/powershell-maintainers @jshigetomi # Now, areas that should have paths or filters, although we might not have them defined # According to the docs, order here must be by precedence of the filter, with later rules overwritting @@ -53,13 +56,13 @@ src/System.Management.Automation/engine/remoting @daxian-dbw @TravisEz13 # Areas: Build # Must be last -*.config @PowerShell/powershell-maintainers -*.props @PowerShell/powershell-maintainers -*.yml @PowerShell/powershell-maintainers -*.csproj @PowerShell/powershell-maintainers -build.* @PowerShell/powershell-maintainers -tools/ @PowerShell/powershell-maintainers -# docker/ @PowerShell/powershell-maintainers +*.config @PowerShell/powershell-maintainers @jshigetomi +*.props @PowerShell/powershell-maintainers @jshigetomi +*.yml @PowerShell/powershell-maintainers @jshigetomi +*.csproj @PowerShell/powershell-maintainers @jshigetomi +build.* @PowerShell/powershell-maintainers @jshigetomi +tools/ @PowerShell/powershell-maintainers @jshigetomi +# docker/ @PowerShell/powershell-maintainers @jshigetomi # Area: Compliance tools/terms @TravisEz13 diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index c90f3046316..35eab8c9b5b 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,47 +1,24 @@ # Contributing to PowerShell -We welcome and appreciate contributions from the community. -There are many ways to become involved with PowerShell: -including filing issues, -joining in design conversations, -writing and improving documentation, -and contributing to the code. -Please read the rest of this document to ensure a smooth contribution process. - -## Intro to Git and GitHub - -* Make sure you have a [GitHub account](https://github.com/signup/free). -* Learning Git: - * GitHub Help: [Good Resources for Learning Git and GitHub][good-git-resources] - * [Git Basics](../docs/git/basics.md): install and getting started -* [GitHub Flow Guide](https://guides.github.com/introduction/flow/): - step-by-step instructions of GitHub Flow - -## Quick Start Checklist +We welcome and appreciate contributions from the community! -* Review the [Contributor License Agreement][CLA] requirement. -* Get familiar with the [PowerShell repository](../docs/git). +There are many ways to become involved with PowerShell including: -## Contributing to Issues +- [Contributing to Documentation](#contributing-to-documentation) +- [Contributing to Issues](#contributing-to-issues) +- [Contributing to Code](#contributing-to-code) -* Review [Issue Management][issue-management]. -* Check if the issue you are going to file already exists in our [GitHub issues][open-issue]. -* If you can't find your issue already, - [open a new issue](https://github.com/PowerShell/PowerShell/issues/new/choose), - making sure to follow the directions as best you can. -* If the issue is marked as [`Up-for-Grabs`][up-for-grabs], - the PowerShell Maintainers are looking for help with the issue. -* 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. +Please read the rest of this document to ensure a smooth contribution process. ## Contributing to Documentation -### Contributing to documentation related to PowerShell +Contributing to the docs is an excellent way to get started with the process of making open source contributions with minimal technical skill required. -Please see the [Contributor Guide in `MicrosoftDocs/PowerShell-Docs`](https://github.com/MicrosoftDocs/PowerShell-Docs/blob/staging/CONTRIBUTING.md). +Please see the [Contributor Guide in `MicrosoftDocs/PowerShell-Docs`](https://aka.ms/PSDocsContributor). -#### Quick steps if you're changing an existing cmdlet +Learn how to [Contribute to Docs like a Microsoft Insider](https://www.youtube.com/watch?v=ZQODV8krq1Q) (by @sdwheeler) + +### Updating Documentation for an existing cmdlet If you made a change to an existing cmdlet and would like to update the documentation using PlatyPS, here are the quick steps: @@ -67,13 +44,13 @@ which will update the documentation for you. made. 1. Link your Docs PR to your original change PR. -### Contributing to documentation related to maintaining or contributing to the PowerShell project +### Style notes for documentation related to maintaining or contributing to the PowerShell project * When writing Markdown documentation, use [semantic linefeeds][]. In most cases, it means "one clause/idea per line". * Otherwise, these issues should be treated like any other issue in this repository. -#### Spell checking documentation +### Spell checking documentation Documentation is spellchecked. We use the [textlint](https://github.com/textlint/textlint/wiki/Collection-of-textlint-rule) command-line tool, @@ -89,7 +66,7 @@ To run the spell checker, follow these steps: If you need to add a term or disable checking part of a file see the [configuration sections of the rule](https://github.com/sapegin/textlint-rule-terminology). -#### Checking links in documentation +### Checking links in documentation Documentation is link-checked. We make use of the `markdown-link-check` command-line tool, @@ -102,21 +79,18 @@ To run the link-checker, follow these steps: `npm install -g markdown-link-check@3.8.5` * run `find . \*.md -exec markdown-link-check {} \;` -## Contributing to Code - -### Code Editor - -You should use the multi-platform [Visual Studio Code (VS Code)][use-vscode-editor]. - -### Building and testing - -#### Building PowerShell - -Please see [Building PowerShell](../README.md#building-the-repository). - -#### Testing PowerShell +## Contributing to Issues -Please see PowerShell [Testing Guidelines - Running Tests Outside of CI][running-tests-outside-of-ci] on how to test your build locally. +1. Review [Issue Management][issue-management]. +1. Check if the issue you are going to file already exists in our [GitHub issues][open-issue]. +1. If you can't find your issue already, + [open a new issue](https://github.com/PowerShell/PowerShell/issues/new/choose), + making sure to follow the directions as best you can. +1. If the issue is marked as [`Up-for-Grabs`][up-for-grabs], + the PowerShell Maintainers are looking for help with the issue. +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 @@ -137,6 +111,59 @@ Additional references: * GitHub's guide on [Contributing to Open Source](https://guides.github.com/activities/contributing-to-open-source/#pull-request) * GitHub's guide on [Understanding the GitHub Flow](https://guides.github.com/introduction/flow/) +## Contributing to Code + +### Quick Start Checklist + +* Review the [Contributor License Agreement][CLA] requirement. +* Get familiar with the [PowerShell Repository Git Concepts](../docs/git/README.md). +* Start a [GitHub Codespace](#Dev Container) and start exploring the repository. +* Consider if what you want to do might be implementable as a [PowerShell Binary Module](https://learn.microsoft.com/powershell/scripting/developer/module/how-to-write-a-powershell-binary-module?view=powershell-7.5). + The PowerShell repository has a rigorous acceptance process due to its huge popularity and emphasis on stability and long term support, and with a binary module you can contribute to the community much more quickly. +* Pick an existing issue to work on! For instance, clarifying a confusing or unclear error message is a great starting point. + +### Intro to Git and GitHub + +1. Sign up for a [GitHub account](https://github.com/signup/free). +1. Learning Git and GitHub: + - [Git Basics](../docs/git/basics.md): install and getting started + - [Good Resources for Learning Git and GitHub][good-git-resources] +1. The PowerShell repository uses GitHub Flow as the primary branching strategy. [Learn about GitHub Flow](https://guides.github.com/introduction/flow/) + +### Code Editing + +PowerShell is primarily written in [C#](https://learn.microsoft.com/dotnet/csharp/tour-of-csharp/overview). While you can use any C# development environment you prefer, [Visual Studio Code][use-vscode-editor] is recommended. + +### Dev Container + +There is a PowerShell [Dev Container](https://code.visualstudio.com/docs/devcontainers/containers) which enables you get up and running quickly with a prepared Visual Studio Code environment with all the required prerequisites already installed. + +[GitHub Codespaces](https://github.com/features/codespaces) is the fastest way to get started. +Codespaces allows you to start a Github-hosted devcontainer from anywhere and contribute from your browser or via Visual Studio Code remoting. +All GitHub users get 15 hours per month of a 4-core codespace for free. + +To start a codespace for the PowerShell repository: + +1. Go to https://github.com/PowerShell/PowerShell +1. Click the green button on the right and choose to create a codespace + + ![alt text](Images/Codespaces.png) +1. Alternatively, just hit the comma `,` key on your keyboard which should instantly start a codespace as well. + +Once the codespace starts, you can press `ctrl+shift+b` (`cmd+shift+b` on Mac) to run the default build task. If you would like to interactivey test your changes, you can press `F5` to start debugging, add breakpoints, etc. + +[Learn more about how to get started with C# in Visual Studio Code](https://code.visualstudio.com/docs/csharp/get-started) + +### Building and Testing + +#### Building PowerShell + +[Building PowerShell](../README.md#Building-Powershell) has instructions for various platforms. + +#### Testing PowerShell + +Please see PowerShell [Testing Guidelines - Running Tests Outside of CI][running-tests-outside-of-ci] on how to test your build locally. + ### Lifecycle of a pull request #### Before submitting @@ -161,7 +188,7 @@ Additional references: In such case, it's better to split the PR to multiple smaller ones. For large features, try to approach it in an incremental way, so that each PR won't be too big. * If you're contributing in a way that changes the user or developer experience, you are expected to document those changes. - See [Contributing to documentation related to PowerShell](#contributing-to-documentation-related-to-powershell). + See [Contributing to documentation related to PowerShell](#contributing-to-documentation). * Add a meaningful title of the PR describing what change you want to check in. Don't simply put: "Fix issue #5". Also don't directly use the issue title as the PR title. @@ -191,10 +218,10 @@ Additional references: As an example, this requirement includes any changes to cmdlets (including cmdlet parameters) and features which have associated about_* topics. While not required, we appreciate any contributors who add this label and create the issue themselves. Even better, all contributors are free to contribute the documentation themselves. - (See [Contributing to documentation related to PowerShell](#contributing-to-documentation-related-to-powershell) for more info.) + (See [Contributing to documentation related to PowerShell](#contributing-to-documentation) for more info.) * If your change adds a new source file, ensure the appropriate copyright and license headers is on top. It is standard practice to have both a copyright and license notice for each source file. - * For `.h`, `.cpp`, and `.cs` files use the copyright header with empty line after it: + * For `.cs` files use the copyright header with empty line after it: ```c# // Copyright (c) Microsoft Corporation. @@ -237,18 +264,10 @@ Additional references: * 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 @@ -266,7 +285,7 @@ Additional references: #### Pull Request - Roles and Responsibilities -1. The PR *author* is responsible for moving the PR forward to get it Approved. +1. The PR *author* is responsible for moving the PR forward to get it approved. This includes addressing feedback within a timely period and indicating feedback has been addressed by adding a comment and mentioning the specific *reviewers*. When updating your pull request, please **create new commits** and **don't rewrite the commits history**. This way it's very easy for the reviewers to see diff between iterations. @@ -300,7 +319,7 @@ In these cases: Mention the original pull request ID in the description of the new issue and close the abandoned pull request. - If the changes in an abandoned pull request are no longer needed (e.g. due to refactoring of the codebase or a design change), *assignee* will simply close the pull request. -## Making Breaking Changes +### Making Breaking Changes When you make code changes, please pay attention to these that can affect the [Public Contract][breaking-changes-contract]. @@ -309,12 +328,12 @@ Before making changes to the code, first review the [breaking changes contract][breaking-changes-contract] and follow the guidelines to keep PowerShell backward compatible. -## Making Design Changes +### Making Design Changes To add new features such as cmdlets or making design changes, please follow the [PowerShell Request for Comments (RFC)][rfc-process] process. -## Common Engineering Practices +### Common Engineering Practices Other than the guidelines for [coding][coding-guidelines], the [RFC process][rfc-process] for design, @@ -366,7 +385,7 @@ is also appropriate, as is using Markdown syntax. Before you invest a large amount of time, file an issue and start a discussion with the community. -## Contributor License Agreement (CLA) +### Contributor License Agreement (CLA) To speed up the acceptance of any contribution to any PowerShell repositories, you should sign the Microsoft [Contributor License Agreement (CLA)](https://cla.microsoft.com/) ahead of time. @@ -404,7 +423,7 @@ Repeat offenses may result in a permanent ban from the PowerShell org. [PowerShell-Docs]: https://github.com/powershell/powershell-docs/ [use-vscode-editor]: https://learn.microsoft.com/dotnet/core/tutorials/with-visual-studio-code [repository-maintainer]: ../docs/community/governance.md#repository-maintainers -[area-expert]: ../docs/community/governance.md#area-experts +[area-expert]: ../.github/CODEOWNERS [first-time-issue]: https://github.com/powershell/powershell/issues?q=is%3Aopen+is%3Aissue+label%3AFirst-Time-Issue [coding-guidelines]: ../docs/dev-process/coding-guidelines.md [breaking-changes-contract]: ../docs/dev-process/breaking-change-contract.md diff --git a/.github/ISSUE_TEMPLATE/Distribution_Request.yaml b/.github/ISSUE_TEMPLATE/Distribution_Request.yaml deleted file mode 100644 index de15814cb3b..00000000000 --- a/.github/ISSUE_TEMPLATE/Distribution_Request.yaml +++ /dev/null @@ -1,64 +0,0 @@ -name: Distribution Support Request -description: Requests support for a new distribution -title: Distribution Support Request -labels: [Distribution-Request, Needs-Triage] -body: -- type: input - attributes: - label: Name of the Distribution - validations: - required: true -- type: input - attributes: - label: Version of the Distribution - validations: - required: true -- type: checkboxes - attributes: - label: Package Types - options: - - label: Deb - - label: RPM - - label: Tar.gz - - label: Snap - Stop! Please file your issue in [PowerShell-Snap](https://github.com/powershell/powershell-snap) instead. -- type: input - attributes: - label: Processor Architecture - description: One per request! - validations: - required: true -- type: checkboxes - attributes: - label: .NET Core Support - description: The following is a requirement for supporting a distribution **without exception.** - options: - - label: The version and architecture of the Distribution is [supported by .NET Core](https://github.com/dotnet/core/blob/master/release-notes/5.0/5.0-supported-os.md#linux). - required: true -- type: checkboxes - attributes: - label: Distribution Requirements - description: The following are requirements for supporting a distribution. - options: - - label: The version of the Distribution is supported for at least one year. - - label: The version of the Distribution is not an [interim release](https://ubuntu.com/about/release-cycle) or equivalent. -- type: input - attributes: - label: Exemption Justification - description: | - Please write a justification for any exception where the above criteria - are not met. The PowerShell committee will review the request. -- type: checkboxes - attributes: - label: Progress - options: - - label: An issue has been filed to create a Docker image in [PowerShell-Docker](https://github.com/powershell/powershell-docker) -- type: checkboxes - attributes: - label: For PowerShell Team **ONLY** - options: - - label: Docker image created - - label: Docker image published - - label: Distribution tested - - label: Update `packages.microsoft.com` deployment - - label: "[Lifecycle](https://github.com/MicrosoftDocs/PowerShell-Docs/blob/staging/reference/docs-conceptual/PowerShell-Support-Lifecycle.md) updated" - - label: Documentation Updated 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/Images/Codespaces.png b/.github/Images/Codespaces.png new file mode 100644 index 00000000000..f37792f5c9f Binary files /dev/null and b/.github/Images/Codespaces.png differ diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index a3dc6fd5198..27089847987 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -14,8 +14,7 @@ - Use the present tense and imperative mood when describing your changes - [ ] [Summarized changes](https://github.com/PowerShell/PowerShell/blob/master/.github/CONTRIBUTING.md#pull-request---submission) - [ ] [Make sure all `.h`, `.cpp`, `.cs`, `.ps1` and `.psm1` files have the correct copyright header](https://github.com/PowerShell/PowerShell/blob/master/.github/CONTRIBUTING.md#pull-request---submission) -- [ ] This PR is ready to merge and is not [Work in Progress](https://github.com/PowerShell/PowerShell/blob/master/.github/CONTRIBUTING.md#pull-request---work-in-progress). - - If the PR is work in progress, please add the prefix `WIP:` or `[ WIP ]` to the beginning of the title (the `WIP` bot will keep its status check at `Pending` while the prefix is present) and remove the prefix when the PR is ready. +- [ ] This PR is ready to merge. If this PR is a work in progress, please open this as a [Draft Pull Request and mark it as Ready to Review when it is ready to merge](https://docs.github.com/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests#draft-pull-requests). - **[Breaking changes](https://github.com/PowerShell/PowerShell/blob/master/.github/CONTRIBUTING.md#making-breaking-changes)** - [ ] None - **OR** @@ -25,21 +24,8 @@ - [ ] Not Applicable - **OR** - [ ] [Documentation needed](https://github.com/PowerShell/PowerShell/blob/master/.github/CONTRIBUTING.md#pull-request---submission) - - [ ] Issue filed: + - [ ] Issue filed: - **Testing - New and feature** - [ ] N/A or can only be tested interactively - **OR** - [ ] [Make sure you've added a new test if existing tests do not effectively test the code changed](https://github.com/PowerShell/PowerShell/blob/master/.github/CONTRIBUTING.md#before-submitting) -- **Tooling** - - [ ] I have considered the user experience from a tooling perspective and don't believe tooling will be impacted. - - **OR** - - [ ] I have considered the user experience from a tooling perspective and opened an issue in the relevant tool repository. This may include: - - [ ] Impact on [PowerShell Editor Services](https://github.com/PowerShell/PowerShellEditorServices) which is used in the [PowerShell extension](https://github.com/PowerShell/vscode-powershell) for VSCode - (which runs in a different PS Host). - - [ ] Issue filed: - - [ ] Impact on Completions (both in the console and in editors) - one of PowerShell's most powerful features. - - [ ] Issue filed: - - [ ] Impact on [PSScriptAnalyzer](https://github.com/PowerShell/PSScriptAnalyzer) (which provides linting & formatting in the editor extensions). - - [ ] Issue filed: - - [ ] Impact on [EditorSyntax](https://github.com/PowerShell/EditorSyntax) (which provides syntax highlighting with in VSCode, GitHub, and many other editors). - - [ ] Issue filed: 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 new file mode 100644 index 00000000000..af23540256d --- /dev/null +++ b/.github/actions/infrastructure/path-filters/action.yml @@ -0,0 +1,137 @@ +name: Path Filters +description: 'Path Filters' +inputs: + GITHUB_TOKEN: + description: 'GitHub token' + required: true +outputs: + source: + description: 'Source code changes (composite of all changes)' + value: ${{ steps.filter.outputs.source }} + githubChanged: + description: 'GitHub workflow changes' + value: ${{ steps.filter.outputs.githubChanged }} + toolsChanged: + description: 'Tools changes' + value: ${{ steps.filter.outputs.toolsChanged }} + propsChanged: + description: 'Props changes' + value: ${{ steps.filter.outputs.propsChanged }} + testsChanged: + description: 'Tests changes' + value: ${{ steps.filter.outputs.testsChanged }} + mainSourceChanged: + description: 'Main source code changes (any changes in src/)' + value: ${{ steps.filter.outputs.mainSourceChanged }} + 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@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + env: + FILES_JSON: ${{ steps.get-files.outputs.files }} + with: + github-token: ${{ inputs.GITHUB_TOKEN }} + script: | + console.log(`Event Name: ${context.eventName}`); + + // Just say everything changed if this is not a PR + if (context.eventName !== 'pull_request') { + console.log('Not a pull request, setting all outputs to true'); + core.setOutput('toolsChanged', true); + core.setOutput('githubChanged', true); + core.setOutput('propsChanged', true); + core.setOutput('testsChanged', true); + core.setOutput('mainSourceChanged', true); + core.setOutput('buildModuleChanged', true); + core.setOutput('source', true); + return; + } + + // 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 toolsChanged = toolsCiPsm1Changed || toolsBuildCommonChanged; + console.log(`→ Tools changed: ${toolsChanged}`); + + const propsChanged = files.some(file => file.endsWith('.props')); + console.log(`✓ Props files changed: ${propsChanged}`); + + const testsChanged = files.some(file => file.startsWith('test/powershell/') || file.startsWith('test/tools/') || file.startsWith('test/xUnit/')); + console.log(`✓ Tests changed: ${testsChanged}`); + + const mainSourceChanged = files.some(file => file.startsWith('src/')); + console.log(`✓ Main source (src/) changed: ${mainSourceChanged}`); + + const buildModuleChanged = files.some(file => file === 'build.psm1'); + console.log(`✓ build.psm1 changed: ${buildModuleChanged}`); + + const globalConfigChanged = files.some(file => file === '.globalconfig' || file === 'nuget.config' || file === 'global.json'); + console.log(`✓ Global config changed: ${globalConfigChanged}`); + + 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); + core.setOutput('propsChanged', propsChanged); + core.setOutput('testsChanged', testsChanged); + core.setOutput('mainSourceChanged', mainSourceChanged); + core.setOutput('buildModuleChanged', buildModuleChanged); + core.setOutput('globalConfigChanged', globalConfigChanged); + core.setOutput('packagingChanged', packagingChanged); + core.setOutput('source', source); 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 03c44a151c7..ab30e0d9ce6 100644 --- a/.github/actions/test/nix/action.yml +++ b/.github/actions/test/nix/action.yml @@ -14,35 +14,94 @@ inputs: required: false default: ctrf type: string + GITHUB_TOKEN: + description: 'GitHub token for API authentication' + required: true 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/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: path: "${{ github.workspace }}" + - name: Capture Artifacts Directory continue-on-error: true - run: Get-ChildItem "${{ github.workspace }}/build/*" -Recurse + run: |- + Import-Module ./build.psm1 + Write-LogGroupStart -Title 'Artifacts Directory' + Get-ChildItem "${{ github.workspace }}/build/*" -Recurse + 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: |- + Import-Module ./build.psm1 + Write-LogGroupStart -Title 'Bootstrap' Import-Module ./tools/ci.psm1 Invoke-CIInstall -SkipUser + 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" @@ -68,7 +127,11 @@ runs: - name: Capture Extracted Build ZIP continue-on-error: true - run: Get-ChildItem "${{ github.workspace }}/bins/*" -Recurse -ErrorAction SilentlyContinue + run: |- + Import-Module ./build.psm1 + Write-LogGroupStart -Title 'Extracted Build ZIP' + Get-ChildItem "${{ github.workspace }}/bins/*" -Recurse -ErrorAction SilentlyContinue + Write-LogGroupEnd -Title 'Extracted Build ZIP' shell: pwsh - name: Test @@ -84,7 +147,7 @@ runs: chmod a+x $pwshPath $options.Output = $pwshPath Set-PSOptions $options - Invoke-CITest -Purpose '${{ inputs.purpose }}' -TagSet '${{ inputs.tagSet }}' -TitlePrefix '${{ inputs.buildName }}' -OutputFormat JUnitXml + Invoke-CITest -Purpose '${{ inputs.purpose }}' -TagSet '${{ inputs.tagSet }}' -TitlePrefix '${{ inputs.buildName }}' -OutputFormat NUnitXml shell: pwsh - name: Convert, Publish, and Upload Pester Test Results diff --git a/.github/actions/test/process-pester-results/action.yml b/.github/actions/test/process-pester-results/action.yml index 758bbdfc353..44f2037626f 100644 --- a/.github/actions/test/process-pester-results/action.yml +++ b/.github/actions/test/process-pester-results/action.yml @@ -10,55 +10,18 @@ inputs: required: false default: "${{ runner.workspace }}/testResults" type: string - ctrfFolder: - required: false - default: ctrf - type: string runs: using: composite steps: - - name: Convert JUnit to CTRF + - name: Log Summary run: |- - Get-ChildItem -Path "${{ inputs.testResultsFolder }}/*.xml" -Recurse | ForEach-Object { - npx --yes junit-to-ctrf $_.FullName --output ./${{ inputs.ctrfFolder }}/$($_.BaseName).json --tool Pester - } + & "$env:GITHUB_ACTION_PATH/process-pester-results.ps1" -Name '${{ inputs.name }}' -TestResultsFolder '${{ inputs.testResultsFolder }}' shell: pwsh - # this task only takes / as directory separators - - name: Publish Test Report - uses: ctrf-io/github-test-reporter@v1 - with: - report-path: './${{ inputs.ctrfFolder }}/*.json' - exit-on-fail: true - summary-report: true - test-report: false - test-list-report: false - failed-report: false - fail-rate-report: false - flaky-report: false - flaky-rate-report: false - failed-folded-report: true - previous-results-report: false - ai-report: true - skipped-report: false - suite-folded-report: false - suite-list-report: false - pull-request-report: false - commit-report: false - custom-report: false - if: always() - - 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 - - - name: Upload ctrf artifact - if: always() - uses: actions/upload-artifact@v4 - with: - name: ctrf-pester-${{ inputs.name }} - path: ${{ inputs.ctrfFolder }} diff --git a/.github/actions/test/process-pester-results/process-pester-results.ps1 b/.github/actions/test/process-pester-results/process-pester-results.ps1 new file mode 100644 index 00000000000..5804bec9a94 --- /dev/null +++ b/.github/actions/test/process-pester-results/process-pester-results.ps1 @@ -0,0 +1,124 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +param( + [parameter(Mandatory)] + [string]$Name, + [parameter(Mandatory)] + [string]$TestResultsFolder +) + +Import-Module "$PSScriptRoot/../../../../build.psm1" + +if (-not $env:GITHUB_STEP_SUMMARY) { + Write-Error "GITHUB_STEP_SUMMARY is not set. Ensure this workflow is running in a GitHub Actions environment." + exit 1 +} + +$testCaseCount = 0 +$testErrorCount = 0 +$testFailureCount = 0 +$testNotRunCount = 0 +$testInconclusiveCount = 0 +$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) + + $testCaseCount += [int]$results.'test-results'.total + $testErrorCount += [int]$results.'test-results'.errors + $testFailureCount += [int]$results.'test-results'.failures + $testNotRunCount += [int]$results.'test-results'.'not-run' + $testInconclusiveCount += [int]$results.'test-results'.inconclusive + $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" + } + } + } +} + +@" + +# Summary of $Name + +- Total Tests: $testCaseCount +- Total Errors: $testErrorCount +- Total Failures: $testFailureCount +- Total Not Run: $testNotRunCount +- Total Inconclusive: $testInconclusiveCount +- Total Ignored: $testIgnoredCount +- Total Skipped: $testSkippedCount +- Total Invalid: $testInvalidCount + +"@ | Out-File -FilePath $ENV:GITHUB_STEP_SUMMARY -Append + +Write-Log "Summary written to $ENV:GITHUB_STEP_SUMMARY" + +Write-LogGroupStart -Title 'Test Results' +Get-Content $ENV:GITHUB_STEP_SUMMARY +Write-LogGroupEnd -Title 'Test Results' + +if ($testErrorCount -gt 0 -or $testFailureCount -gt 0) { + Write-Error "There were $testErrorCount/$testFailureCount errors/failures in the test results." + exit 1 +} +if ($testCaseCount -eq 0) { + Write-Error "No test cases were run." + exit 1 +} 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 d2af55ce5a9..ddc5da4d664 100644 --- a/.github/actions/test/windows/action.yml +++ b/.github/actions/test/windows/action.yml @@ -14,31 +14,66 @@ inputs: required: false default: ctrf type: string + GITHUB_TOKEN: + description: 'GitHub token for API authentication' + required: true 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/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: path: "${{ github.workspace }}" + - name: Capture Artifacts Directory continue-on-error: true - run: Get-ChildItem "${{ github.workspace }}\build\*" -Recurse + run: |- + Import-Module ./build.psm1 + Write-LogGroupStart -Title 'Artifacts Directory' + Get-ChildItem "${{ github.workspace }}/build/*" -Recurse + 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: |- - # Remove "Program Files\dotnet" from the env variable PATH, so old SDKs won't affect us. + Import-Module ./build.psm1 + Write-LogGroupStart -Title 'Bootstrap' Write-Host "Old Path:" Write-Host $env:Path $dotnetPath = Join-Path $env:SystemDrive 'Program Files\dotnet' @@ -49,6 +84,7 @@ runs: # Bootstrap Import-Module .\tools\ci.psm1 Invoke-CIInstall + Write-LogGroupEnd -Title 'Bootstrap' - name: Test if: success() @@ -60,7 +96,7 @@ runs: $path = split-path -path $options.Output $rootPath = split-Path -path $path Expand-Archive -Path '${{ github.workspace }}\build\build.zip' -DestinationPath $rootPath -Force - Invoke-CITest -Purpose '${{ inputs.purpose }}' -TagSet '${{ inputs.tagSet }}' -OutputFormat JUnitXml + Invoke-CITest -Purpose '${{ inputs.purpose }}' -TagSet '${{ inputs.tagSet }}' -OutputFormat NUnitXml shell: pwsh - name: Convert, Publish, and Upload Pester Test Results 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 d9c4da0efb7..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@9ff30cde74284045941af16a04362938957253b1 # v1.1.17 diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index fea1d11c9dd..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@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4.5.0 + uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml index a580c1f5185..27ceac59bbd 100644 --- a/.github/workflows/labels.yml +++ b/.github/workflows/labels.yml @@ -13,16 +13,16 @@ permissions: jobs: verify-labels: - if: startsWith(github.repository_owner, 'azure') || github.repository_owner == 'PowerShell' + if: github.repository_owner == 'PowerShell' runs-on: ubuntu-latest 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 54282fc62f6..77186125a9c 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -9,8 +9,11 @@ on: branches: - master - release/** + - github-mirror paths: - "**" + - "*" + - ".globalconfig" - "!.github/ISSUE_TEMPLATE/**" - "!.dependabot/config.yml" - "!.pipelines/**" @@ -19,15 +22,17 @@ on: branches: - 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 @@ -43,39 +48,47 @@ jobs: # Required permissions permissions: pull-requests: read + contents: read + # 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 - # For pull requests it's not necessary to checkout the code - - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.2.0 + - name: Change Detection id: filter + uses: "./.github/actions/infrastructure/path-filters" with: - list-files: json - filters: .github/action-filters.yml + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Capture outputs - run: | - "source: ${{ steps.filter.outputs.source }}" - "github: ${{ steps.filter.outputs.github }}" - "tools: ${{ steps.filter.outputs.tools }}" - "props: ${{ steps.filter.outputs.props }}" - "tests: ${{ steps.filter.outputs.tests }}" - "mainSource: ${{ steps.filter.outputs.mainSource }}" - "buildModule: ${{ steps.filter.outputs.buildModule }}" - shell: pwsh + 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 @@ -86,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 @@ -98,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 @@ -115,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 @@ -132,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 @@ -149,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 + + - name: Run Infrastructure Tests + shell: pwsh + run: | + $testResultsFolder = Join-Path $PWD "testResults" + New-Item -ItemType Directory -Path $testResultsFolder -Force | Out-Null - 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 + $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' - 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@1b549b9259bda1cb5ddde3b41741a82a2d15a841 # v3.28.13 - 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@1b549b9259bda1cb5ddde3b41741a82a2d15a841 # v3.28.13 + $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 fcb69332740..55d852bb68a 100644 --- a/.github/workflows/macos-ci.yml +++ b/.github/workflows/macos-ci.yml @@ -7,8 +7,11 @@ on: branches: - master - release/** + - github-mirror paths: - "**" + - "*" + - ".globalconfig" - "!.github/ISSUE_TEMPLATE/**" - "!.dependabot/config.yml" - "!.pipelines/**" @@ -17,15 +20,17 @@ on: branches: - 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 @@ -34,6 +39,7 @@ env: __SuppressAnsiEscapeSequences: 1 nugetMultiFeedWarnLevel: none system_debug: 'false' + jobs: changes: name: Change Detection @@ -42,39 +48,31 @@ jobs: # Required permissions permissions: pull-requests: read + contents: read + # 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 - # For pull requests it's not necessary to checkout the code - - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.2.0 + - name: Change Detection id: filter + uses: "./.github/actions/infrastructure/path-filters" with: - list-files: json - filters: .github/action-filters.yml - - - name: Capture outputs - run: | - "source: ${{ steps.filter.outputs.source }}" - "github: ${{ steps.filter.outputs.github }}" - "tools: ${{ steps.filter.outputs.tools }}" - "props: ${{ steps.filter.outputs.props }}" - "tests: ${{ steps.filter.outputs.tests }}" - "mainSource: ${{ steps.filter.outputs.mainSource }}" - "buildModule: ${{ steps.filter.outputs.buildModule }}" - shell: pwsh + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 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 @@ -84,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 @@ -96,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 @@ -113,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 @@ -130,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 @@ -147,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 27e0f9eff5d..00000000000 --- a/.github/workflows/markdownLink.yml +++ /dev/null @@ -1,44 +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: Lint Markdown - uses: super-linter/super-linter@4e8a7c2bf106c4c766c816b35ec612638dc9b6b2 # v7.3.0 - env: - VALIDATE_ALL_CODEBASE: false - DEFAULT_BRANCH: master - FILTER_REGEX_INCLUDE: .*\.md - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - VALIDATE_EDITORCONFIG: false - VALIDATE_JSCPD: false - VALIDATE_CHECKOV: false diff --git a/.github/workflows/markdownLinkDaily.yml b/.github/workflows/markdownLinkDaily.yml deleted file mode 100644 index 563790e39ca..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@190d4d92146df11f854709774a4dae6eaf5e2aa3 # 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 339234a3c56..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@96f2ec2e1a7a53ead156504922e9bc36d64f49ee # v1.0.16 diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index a8e136cc63f..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@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1 + 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@1b549b9259bda1cb5ddde3b41741a82a2d15a841 # v3.28.13 + 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 a18029bfbe0..8a57b8b9726 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -5,8 +5,11 @@ on: branches: - master - release/** + - github-mirror paths: - "**" + - "*" + - ".globalconfig" - "!.vsts-ci/misc-analysis.yml" - "!.github/ISSUE_TEMPLATE/**" - "!.dependabot/config.yml" @@ -16,10 +19,13 @@ on: branches: - 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: @@ -29,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 @@ -43,39 +51,31 @@ jobs: # Required permissions permissions: pull-requests: read + contents: read + # 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 - # For pull requests it's not necessary to checkout the code - - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.2.0 + - name: Change Detection id: filter + uses: "./.github/actions/infrastructure/path-filters" with: - list-files: json - filters: .github/action-filters.yml - - - name: Capture outputs - run: | - "source: ${{ steps.filter.outputs.source }}" - "github: ${{ steps.filter.outputs.github }}" - "tools: ${{ steps.filter.outputs.tools }}" - "props: ${{ steps.filter.outputs.props }}" - "tests: ${{ steps.filter.outputs.tests }}" - "mainSource: ${{ steps.filter.outputs.mainSource }}" - "buildModule: ${{ steps.filter.outputs.buildModule }}" - shell: pwsh + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 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 @@ -85,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 @@ -97,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 @@ -114,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 @@ -131,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 @@ -148,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 new file mode 100644 index 00000000000..2461d3cd310 --- /dev/null +++ b/.pipelines/MSIXBundle-vPack-Official.yml @@ -0,0 +1,414 @@ +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: netiso + displayName: "Network Isolation Policy" + type: string + values: + - KS4 + - R1 + - Netlock + default: "R1" + +name: msixbundle_vPack_$(Build.SourceBranchName)_Prod.True_Create.${{ parameters.createVPack }}_$(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/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: onebranchTemplates + type: git + name: OneBranch.Pipelines/GovernedTemplates + ref: refs/heads/main + +extends: + 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 + # 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: + - stage: Build_MSIX_Package + displayName: 'Build and create MSIX packages' + dependsOn: [] + jobs: + - job: Build + pool: + type: windows + + strategy: + matrix: + x64: + Architecture: x64 + arm64: + Architecture: arm64 + + variables: + ArtifactPlatform: 'windows' + ob_outputDirectory: '$(BUILD.SOURCESDIRECTORY)\out' + ob_artifactBaseName: drop_build_$(Architecture) + + 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 + + # 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) + + # 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" } + } + + $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) + + $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)" + } + + $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 6c84067ce47..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,188 +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 - - 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 30b9e415215..8afce29ede7 100644 --- a/.pipelines/PowerShell-Packages-Official.yml +++ b/.pipelines/PowerShell-Packages-Official.yml @@ -24,42 +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)] + - template: templates/variables/PowerShell-Packages-Variables.yml + parameters: + debug: ${{ parameters.debug }} + ForceAzureBlobDelete: ${{ parameters.ForceAzureBlobDelete }} + ReleaseTagVar: ${{ parameters.ReleaseTagVar }} + disableNetworkIsolation: ${{ parameters.disableNetworkIsolation }} resources: pipelines: @@ -72,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 @@ -86,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. @@ -103,150 +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: upload - dependsOn: [mac_package, windows_package, linux_package, nupkg] - 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 bb83c98ae81..3528e6b1471 100644 --- a/.pipelines/PowerShell-Release-Official.yml +++ b/.pipelines/PowerShell-Release-Official.yml @@ -17,51 +17,30 @@ parameters: # parameters are shown up in ADO UI in a build queue time displayName: Skip Signing type: string default: 'NO' - - name: SkipPMCPublish - displayName: Skip PMC Publish + - 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)-$(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,361 +94,9 @@ extends: tsaOptionsFile: .config\tsaoptions.json stages: - - stage: setReleaseTagAndUploadTools - displayName: 'Set Release Tag and Upload Tools' - jobs: - - template: /.pipelines/templates/release-SetTagAndTools.yml@self - - - stage: msixbundle - displayName: 'Create MSIX Bundle' - dependsOn: [] - jobs: - - template: /.pipelines/templates/release-create-msix.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 - - msixbundle - - 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: PublishGitHubRelease - displayName: Publish GitHub Release - dependsOn: - - setReleaseTagAndUploadTools - - UpdateChangeLog - variables: - ob_release_environment: Production - jobs: - - template: /.pipelines/templates/release-githubtasks.yml@self - - - stage: PushGitTagAndMakeDraftPublic - displayName: Push Git Tag and Make Draft Public - dependsOn: PublishGitHubRelease - 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: PublishNuGet - displayName: Publish NuGet - dependsOn: - - setReleaseTagAndUploadTools - - PushGitTagAndMakeDraftPublic - variables: - ob_release_environment: Production - jobs: - - template: /.pipelines/templates/release-publish-nuget.yml@self - parameters: - skipPublish: true - - - 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: ReleaseDocker - dependsOn: PushGitTagAndMakeDraftPublic - displayName: 'Docker Release' - jobs: - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Start Docker Release - jobName: StartDockerRelease - instructions: | - Kickoff docker release - - - 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\ -- --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 vPack Release pipeline - jobName: PublishVPack - instructions: | - Kick off vPack release 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: - - PublishNuGet - - 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: ReleaseSnap - displayName: Release Snap - dependsOn: ChangesToMaster - jobs: - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Publish Snap - jobName: PublishSnapJob - instructions: | - Publish Snap - - - 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 - - ReleaseSnap - 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 8a3679d1210..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: tools/releaseBuild/azureDevOps/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 f4fd167d7a0..df5ebaac091 100644 --- a/.pipelines/apiscan-gen-notice.yml +++ b/.pipelines/apiscan-gen-notice.yml @@ -8,11 +8,11 @@ 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. + # A PAT in the wrong org will give a single Error 203. No PAT will give a single Error 401, and individual pdbs may be missing even if permissions are correct. + - group: symbols - name: ob_outputDirectory value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' - name: CDP_DEFINITION_BUILD_COUNT @@ -23,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') }}: @@ -80,16 +80,19 @@ 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. -#softwareFolder - relative path to a folder to be scanned. Default value is root of artifacts folder. -#symbolsFolder - relative path to a folder that contains symbols. Default value is root of artifacts folder. - + symbolsFolder: $(SymbolsServerUrl);$(ob_outputDirectory) +#softwareFolder - relative path to a folder to be scanned. Default value is root of artifacts folder tsaOptionsFile: .config\tsaoptions.json + psscriptanalyzer: + enabled: true + policyName: Microsoft + break: false stages: - stage: APIScan @@ -106,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 5f2c098b682..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 detect 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") @@ -51,7 +20,7 @@ steps: $REPOROOT = $env:REPOROOT if (-not (Test-Path $REPOROOT/tools/releaseBuild/setReleaseTag.ps1)) { - if ((Test-Path "$REPOROOT/PowerShell/tools/releaseBuild/setReleaseTag.ps1")) { + if (Test-Path "$REPOROOT/PowerShell/tools/releaseBuild/setReleaseTag.ps1") { $REPOROOT = "$REPOROOT/PowerShell" } else { throw "Could not find setReleaseTag.ps1 in $REPOROOT/tools/releaseBuild or $REPOROOT/PowerShell/tools/releaseBuild" @@ -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 9999 -Stream | write-Verbose -Verbose + 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 4e945b40349..b5a15699026 100644 --- a/.pipelines/templates/compliance/apiscan.yml +++ b/.pipelines/templates/compliance/apiscan.yml @@ -4,23 +4,17 @@ jobs: - job: APIScan variables: - - name: runCodesignValidationInjection - value : false - name: NugetSecurityAnalysisWarningLevel value: none - name: ReleaseTagVar value: fromBranch # Defines the variables APIScanClient, APIScanTenant and APIScanSecret - group: PS-PS-APIScan - # PAT permissions NOTE: Declare a SymbolServerPAT variable in this group with a 'microsoft' organizanization scoped PAT with 'Symbols' Read permission. - # A PAT in the wrong org will give a single Error 203. No PAT will give a single Error 401, and individual pdbs may be missing even if permissions are correct. - - group: symbols - name: branchCounterKey value: $[format('{0:yyyyMMdd}-{1}', pipeline.startTime,variables['Build.SourceBranch'])] - 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 @@ -54,8 +48,7 @@ jobs: - template: ../SetVersionVariables.yml parameters: ReleaseTagVar: $(ReleaseTagVar) - CreateJson: yes - UseJson: no + CreateJson: no - template: ../insert-nuget-config-azfeed.yml parameters: @@ -78,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') @@ -124,11 +89,38 @@ 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 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 -Path $_.FullName -Recurse -Force + } + + # 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 -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' + - task: CodeQL3000Finalize@0 # Add CodeQL Finalize task right after your 'Build' step. displayName: 🔏 CodeQL 3000 Finalize condition: eq(variables['CODEQL_ENABLED'], 'true') diff --git a/.pipelines/templates/compliance/generateNotice.yml b/.pipelines/templates/compliance/generateNotice.yml index b9d489795b1..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' @@ -84,17 +75,6 @@ jobs: displayName: Capture Notice continueOnError: true - - powershell: | - [System.Net.ServicePointManager]::SecurityProtocol = - [System.Net.ServicePointManager]::SecurityProtocol -bor - [System.Security.Authentication.SslProtocols]::Tls12 -bor - [System.Security.Authentication.SslProtocols]::Tls11 - - Set-ItemProperty -Path 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\.NetFramework\v4.0.30319' -Name 'SchUseStrongCrypto' -Value '1' -Type DWord - Set-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\.NetFramework\v4.0.30319' -Name 'SchUseStrongCrypto' -Value '1' -Type DWord - Get-PackageProvider -Name NuGet -ForceBootstrap - displayName: Initalize PowerShellGet - - task: AzurePowerShell@5 displayName: Upload Notice inputs: 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 37a5ed48b07..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 @@ -173,56 +227,70 @@ jobs: Get-ChildItem -Path $(Pipeline.Workspace) -Filter "*.zip" -File | Write-Verbose -Verbose displayName: Compress package files for signing - - task: SFP.build-tasks.custom-build-task-1.EsrpCodeSigning@5 - displayName: 'ESRP CodeSigning' + - task: onebranch.pipeline.signing@1 + displayName: 'OneBranch CodeSigning Package' inputs: - ConnectedServiceName: 'ESRPMacOSSigning' - AppRegistrationClientId: '$(AppRegistrationClientId)' - AppRegistrationTenantId: '$(AppRegistrationTenantId)' - AuthAKVName: 'pwsh-CICD-Keyvault' - AuthCertName: 'PS-macos-signing' - AuthSignCertName: 'ESRP-OneCert' # this is not needed for pkg signing - FolderPath: $(Pipeline.Workspace) - Pattern: '*.zip' - signConfigType: inlineSignParams - inlineOperation: | - [{ - "KeyCode": "$(KeyCode)", - "OperationSetCode": "MacAppDeveloperSign", - "parameters": [ - { - "parameterName": "hardening", - "parameterValue": "enable" - }, - { - "parameterName": "OpusInfo", - "parameterValue": "http://Microsoft.com" + command: 'sign' + files_to_sign: '**/*-osx-*.zip' + search_root: '$(Pipeline.Workspace)' + inline_operation: | + [ + { + "KeyCode": "$(apple_cert_id)", + "OperationCode": "MacAppDeveloperSign", + "ToolName": "sign", + "ToolVersion": "1.0", + "Parameters": { + "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" - }] - SessionTimeout: 90 - ServiceEndpointUrl: '$(ServiceEndpointUrl)' - MaxConcurrency: 25 + "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: $_" + Expand-Archive -Path $_ -DestinationPath $expandDir -Verbose + } - if (-not (Test-Path $_)) { - throw "Package not found: $_" - } - - if (-not (Test-Path $env:ob_outputDirectory)) { - $null = New-Item -Path $env:ob_outputDirectory -ItemType Directory - } + # 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 $env:ob_outputDirectory -Verbose + $pkgFile | ForEach-Object { + Move-Item -Path $_ -Destination $(ob_outputDirectory) -Verbose } Write-Verbose -Verbose "Expanded pkg file:" - Get-ChildItem -Path $env:ob_outputDirectory | Write-Verbose -Verbose + Get-ChildItem -Path $(ob_outputDirectory) | Write-Verbose -Verbose displayName: Expand signed 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 c1f7616110c..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\net9.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\net9.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\net9.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\net9.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\net9.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\net9.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 b77c991cf1f..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,35 +83,10 @@ 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 -- task: onebranch.pipeline.signing@1 - displayName: Sign pwsh.exe with Windows cert - inputs: - command: 'sign' - cp_code: '203' - files_to_sign: '**\pwsh.exe' - search_root: $(Pipeline.Workspace)/toBeSigned - -- pwsh: | - if (Test-Path $(Pipeline.Workspace)/toBeSigned/pwsh.exe) { - Write-Verbose -Verbose "pwsh.exe is found, verifying signature" - $signature = Get-AuthenticodeSignature -FilePath $(Pipeline.Workspace)/toBeSigned/pwsh.exe - if ($signature.SignerCertificate.Issuer -notmatch '^CN=Microsoft Windows Production.*') { - Write-Error -ErrorAction Stop "pwsh.exe is not signed by Microsoft" - } - else { - Write-Verbose -Verbose "pwsh.exe is signed by Microsoft" - } - } - else { - Write-Verbose -Verbose "pwsh.exe is not found, skipping" - } - - displayName: 'Verify windows signature' - - pwsh : | Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose displayName: Capture environment @@ -120,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)" @@ -162,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 @@ -178,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 new file mode 100644 index 00000000000..97d2f4fc46a --- /dev/null +++ b/.pipelines/templates/package-create-msix.yml @@ -0,0 +1,154 @@ +parameters: + - name: OfficialBuild + type: boolean + default: false + +jobs: +- job: CreateMSIXBundle + displayName: Create .msixbundle file + pool: + type: windows + + 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_arm64 + itemPattern: | + **/*.msix + targetPath: '$(Build.ArtifactStagingDirectory)/downloads' + displayName: Download windows arm64 packages + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifact: drop_windows_package_x64 + itemPattern: | + **/*.msix + targetPath: '$(Build.ArtifactStagingDirectory)/downloads' + displayName: Download windows x64 packages + + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + 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 + if ($cmd) { + Write-Verbose -Verbose 'makeappx available in PATH' + $exePath = $cmd.Source + } else { + $toolsDir = '$(Pipeline.Workspace)\releasePipeline\tools' + New-Item $toolsDir -Type Directory -Force > $null + $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:' + } + $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 + + $msixFiles = Get-ChildItem -Path "$(Build.ArtifactStagingDirectory)/downloads/*.msix" -Recurse + foreach ($msixFile in $msixFiles) { + $null = Copy-Item -Path $msixFile.FullName -Destination $sourceDir -Force -Verbose + } + + $makeappx = '$(MakeAppxPath)' + $outputDir = "$sourceDir\output" + New-Item $outputDir -Type Directory -Force > $null + + # 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" + displayName: Create MsixBundle + retryCountOnTaskFailure: 1 + + - 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: | + $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 + } + + foreach ($bundle in $signedBundles) { + Copy-Item -Path $bundle.FullName -Destination "$(ob_outputDirectory)" -Verbose + } + + 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-SetTagAndTools.yml b/.pipelines/templates/release-SetTagAndChangelog.yml similarity index 59% rename from .pipelines/templates/release-SetTagAndTools.yml rename to .pipelines/templates/release-SetTagAndChangelog.yml index 7b8a946e323..b33e652b3c7 100644 --- a/.pipelines/templates/release-SetTagAndTools.yml +++ b/.pipelines/templates/release-SetTagAndChangelog.yml @@ -1,6 +1,6 @@ jobs: -- job: SetTagAndTools - displayName: Set Tag and Tools +- job: setTagAndChangelog + displayName: Set Tag and Upload Changelog condition: succeeded() pool: type: windows @@ -20,32 +20,6 @@ jobs: env: ob_restore_phase: true - - checkout: PSInternalTools - clean: true - env: - ob_restore_phase: true - - - pwsh: | - New-Item -ItemType Directory -Path '$(Pipeline.Workspace)/ToolArtifact' - Get-ChildItem -Path '$(Build.SourcesDirectory)/Internal-PowerShellTeam-Tools/Scripts' -Filter 'GitHubRelease.psm1' -ErrorAction SilentlyContinue | - Copy-Item -Destination '$(Pipeline.Workspace)/ToolArtifact' -Verbose - displayName: Move GitHub Tool - - - task: onebranch.pipeline.signing@1 - displayName: Sign Tools - inputs: - command: 'sign' - signing_profile: internal_azure_service - files_to_sign: '*.ps1;*.psm1' - search_root: '$(Pipeline.Workspace)/ToolArtifact' - - - pwsh: | - Write-Verbose -Verbose "Creating output directory for release tools: $(ob_outputDirectory)/ToolArtifact" - New-Item -Path $(ob_outputDirectory)/ToolArtifact -ItemType Directory -Force - Get-ChildItem -Path "$(Pipeline.Workspace)/ToolArtifact/*" -Recurse | - Copy-Item -Destination $(ob_outputDirectory)/ToolArtifact -Recurse -Verbose - displayName: Upload Tools - - pwsh: | Write-Verbose -Verbose "Release Tag: $(OutputReleaseTag.releaseTag)" $releaseVersion = '$(OutputReleaseTag.releaseTag)' -replace '^v','' @@ -73,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-create-msix.yml b/.pipelines/templates/release-create-msix.yml deleted file mode 100644 index 3714e623b5e..00000000000 --- a/.pipelines/templates/release-create-msix.yml +++ /dev/null @@ -1,105 +0,0 @@ -jobs: -- job: CreateMSIXBundle - displayName: Create .msixbundle file - pool: - type: windows - - variables: - - group: msixTools - - group: 'Azure Blob variable group' - - name: ob_outputDirectory - value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' - - steps: - - template: release-SetReleaseTagandContainerName.yml@self - - - download: PSPackagesOfficial - artifact: drop_windows_package_package_win_arm64 - displayName: Download arm64 msix - patterns: '**/*.msix' - - - download: PSPackagesOfficial - artifact: drop_windows_package_package_win_x64 - displayName: Download x64 msix - patterns: '**/*.msix' - - - download: PSPackagesOfficial - artifact: drop_windows_package_package_win_x86 - displayName: Download x86 msix - patterns: '**/*.msix' - - # Finds the makeappx tool on the machine with image: 'onebranch.azurecr.io/windows/ltsc2022/vse2022:latest' - - pwsh: | - $cmd = Get-Command makeappx.exe -ErrorAction Ignore - if ($cmd) { - Write-Verbose -Verbose 'makeappx available in PATH' - $exePath = $cmd.Source - } else { - $toolsDir = '$(Pipeline.Workspace)\releasePipeline\tools' - New-Item $toolsDir -Type Directory -Force > $null - $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:' - } - $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 - - $msixFiles = Get-ChildItem -Path "$(Pipeline.Workspace)/PSPackagesOfficial/*.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" - - Get-ChildItem -Path $sourceDir -Recurse - $vstsCommandString = "vso[task.setvariable variable=BundleDir]$outputDir" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - displayName: Create MsixBundle - retryCountOnTaskFailure: 1 - - - task: AzurePowerShell@5 - displayName: Upload msix to blob - inputs: - azureSubscription: az-blob-cicd-infra - scriptType: inlineScript - azurePowerShellVersion: LatestVersion - pwsh: true - inline: | - $containerName = '$(OutputVersion.AzureVersion)-private' - $storageAccount = '$(StorageAccount)' - - $storageContext = New-AzStorageContext -StorageAccountName $storageAccount -UseConnectedAccount - - if ($env:BundleDir) { - $bundleFile = Get-Item "$env:BundleDir\*.msixbundle" - $blobName = $bundleFile | Split-Path -Leaf - $existing = Get-AzStorageBlob -Container $containerName -Blob $blobName -Context $storageContext -ErrorAction Ignore - if ($existing) { - Write-Verbose -Verbose "MSIX bundle already exists at '$storageAccount/$containerName/$blobName', removing first." - $existingBlob | Remove-AzStorageBlob -ErrorAction Stop -Verbose - } - - Write-Verbose -Verbose "Uploading $bundleFile to $containerName/$blobName" - Set-AzStorageBlobContent -File $bundleFile -Container $containerName -Blob $blobName -Context $storageContext -Force - } - else{ - throw "BundleDir not found" - } 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 new file mode 100644 index 00000000000..27a358c7e75 --- /dev/null +++ b/.pipelines/templates/release-githubNuget.yml @@ -0,0 +1,228 @@ +parameters: + - name: skipPublish + type: boolean + +jobs: +- job: GithubReleaseDraft + displayName: Create GitHub Release Draft + condition: succeeded() + pool: + type: release + os: windows + templateContext: + inputs: + - input: pipelineArtifact + artifactName: drop_setReleaseTagAndChangelog_SetTagAndChangelog + - input: pipelineArtifact + pipeline: PSPackagesOfficial + artifactName: drop_upload_upload_packages + variables: + - 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 Env: | Out-String -Stream | Write-Verbose -Verbose + displayName: 'Capture Environment Variables' + + - task: PowerShell@2 + inputs: + 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 | + ForEach-Object { + Write-Verbose -Verbose "Generating checksum file for $($_.FullName)" + $packageName = $_.Name + $hash = (Get-FileHash -Path $_.FullName -Algorithm SHA256).Hash.ToLower() + # the '*' before the packagename signifies it is a binary + "$hash *$packageName" + } + $checksums | Out-File -FilePath $OutputPath -Force + $fileContent = Get-Content -Path $OutputPath -Raw | Out-String + Write-Verbose -Verbose -Message $fileContent + displayName: Add sha256 hashes + + - task: PowerShell@2 + inputs: + targetType: inline + script: | + Get-ChildItem $(Pipeline.Workspace) -recurse | Select-Object -ExpandProperty FullName + displayName: List all files in the workspace + + - task: PowerShell@2 + inputs: + targetType: inline + script: | + $releaseVersion = '$(ReleaseTag)' -replace '^v','' + Write-Verbose -Verbose "Available modules: " + Get-Module | Write-Verbose -Verbose + + $filePath = Get-ChildItem -Path "$(Pipeline.Workspace)/CHANGELOG" -Filter '*.md' | Select-Object -First 1 -ExpandProperty FullName + + if (-not (Test-Path $filePath)) { + throw "$filePath not found" + } + + $changelog = Get-Content -Path $filePath + + $headingPattern = "^## \[\d+\.\d+\.\d+" + $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] + 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 + $StringBuilder.AppendLine("### SHA256 Hashes of the release artifacts").AppendLine() > $null + Get-ChildItem -Path "$(Pipeline.Workspace)/GitHubPackages/" -File | ForEach-Object { + $PackageName = $_.Name + $SHA256 = (Get-FileHash -Path $_.FullName -Algorithm SHA256).Hash + $StringBuilder.AppendLine("- $PackageName").AppendLine(" - $SHA256") > $null + } + + $clContent = $StringBuilder.ToString() + + Write-Verbose -Verbose "Selected content: `n$clContent" + + $releaseNotesFilePath = "$(Pipeline.Workspace)/release-notes.md" + $clContent | Out-File -FilePath $releaseNotesFilePath -Encoding utf8 + + Write-Host "##vso[task.setvariable variable=ReleaseNotesFilePath;]$releaseNotesFilePath" + + #if name has prelease then make prerelease true as a variable + if ($releaseVersion -like '*-*') { + Write-Host "##vso[task.setvariable variable=IsPreRelease;]true" + } else { + Write-Host "##vso[task.setvariable variable=IsPreRelease;]false" + } + displayName: Set variables for GitHub release task + + - task: PowerShell@2 + inputs: + targetType: inline + script: | + Write-Host "ReleaseNotes content:" + Get-Content "$(Pipeline.Workspace)/release-notes.md" -Raw | Out-String -width 9999 | Write-Host + displayName: Verify Release Notes + + - task: PowerShell@2 + inputs: + targetType: inline + script: | + $middleURL = '' + $tagString = "$(ReleaseTag)" + Write-Verbose -Verbose "Use the following command to push the tag:" + if ($tagString -match '-preview') { + $middleURL = "preview" + } + elseif ($tagString -match '(\d+\.\d+)') { + $middleURL = $matches[1] + } + $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 + + - task: GitHubRelease@1 + inputs: + gitHubConnection: GitHubReleasePAT + repositoryName: PowerShell/PowerShell + target: master + assets: '$(Pipeline.Workspace)/GitHubPackages/*' + tagSource: 'userSpecifiedTag' + tag: '$(ReleaseTag)' + title: "$(ReleaseTag) Release of PowerShell" + isDraft: true + addChangeLog: false + action: 'create' + releaseNotesFilePath: '$(ReleaseNotesFilePath)' + isPrerelease: '$(IsPreRelease)' + +- job: NuGetPublish + displayName: Publish to NuGet + condition: succeeded() + pool: + type: release + os: windows + templateContext: + inputs: + - input: pipelineArtifact + pipeline: PSPackagesOfficial + artifactName: drop_upload_upload_packages + variables: + - template: ./variables/release-shared.yml@self + parameters: + VERSION: $[ stageDependencies.setReleaseTagAndChangelog.SetTagAndChangelog.outputs['OutputVersion.Version'] ] + + steps: + - task: PowerShell@2 + inputs: + targetType: inline + script: | + Write-Verbose -Verbose "Version: $(Version)" + Get-ChildItem Env: | Out-String -width 9999 -Stream | write-Verbose -Verbose + displayName: 'Capture Environment Variables' + + - task: PowerShell@2 + inputs: + targetType: inline + script: | + #Exclude all global tool packages. Their names start with 'PowerShell.' + $null = New-Item -ItemType Directory -Path "$(Pipeline.Workspace)/release" + Copy-Item "$(Pipeline.Workspace)/NuGetPackages/*.nupkg" -Destination "$(Pipeline.Workspace)/release" -Exclude "PowerShell.*.nupkg" -Force -Verbose + + $releaseVersion = '$(Version)' + $globalToolPath = "$(Pipeline.Workspace)/NuGetPackages/PowerShell.$releaseVersion.nupkg" + + if ($releaseVersion -notlike '*-*') { + # Copy the global tool package for stable releases + Copy-Item $globalToolPath -Destination "$(Pipeline.Workspace)/release" + } + + Write-Verbose -Verbose "The .nupkgs below will be pushed:" + Get-ChildItem "$(Pipeline.Workspace)/release" -recurse + displayName: Download and capture nupkgs + condition: and(ne('${{ parameters.skipPublish }}', 'true'), succeeded()) + + - task: NuGetCommand@2 + displayName: 'NuGet push' + condition: and(ne('${{ parameters.skipPublish }}', 'true'), succeeded()) + inputs: + command: push + packagesToPush: '$(Pipeline.Workspace)/release/*.nupkg' + nuGetFeedType: external + publishFeedCredentials: PowerShellNuGetOrgPush diff --git a/.pipelines/templates/release-githubtasks.yml b/.pipelines/templates/release-githubtasks.yml deleted file mode 100644 index acdb45e18fb..00000000000 --- a/.pipelines/templates/release-githubtasks.yml +++ /dev/null @@ -1,88 +0,0 @@ -jobs: -- job: GithubReleaseDraft - displayName: Create GitHub Release Draft - condition: succeeded() - pool: - type: release - os: windows - templateContext: - inputs: - - input: pipelineArtifact - artifactName: drop_setReleaseTagAndUploadTools_SetTagAndTools - - input: pipelineArtifact - pipeline: PSPackagesOfficial - artifactName: drop_upload_upload_packages - variables: - - template: ./variable/release-shared.yml@self - parameters: - RELEASETAG: $[ stageDependencies.setReleaseTagAndUploadTools.SetTagAndTools.outputs['OutputReleaseTag.releaseTag'] ] - - steps: - - task: PowerShell@2 - inputs: - targetType: inline - script: | - Write-Verbose -Verbose "Release Tag: $(ReleaseTag)" - Get-ChildItem Env: | Out-String -Stream | Write-Verbose -Verbose - displayName: 'Capture Environment Variables' - - - template: release-install-pwsh.yml - - - task: PowerShell@2 - inputs: - targetType: inline - pwsh: true - script: | - $Path = "$(Pipeline.Workspace)/GitHubPackages" - $OutputPath = Join-Path $Path 'hashes.sha256' - $packages = Get-ChildItem -Path $Path -Include * -Recurse -File - $checksums = $packages | - ForEach-Object { - Write-Verbose -Verbose "Generating checksum file for $($_.FullName)" - $packageName = $_.Name - $hash = (Get-FileHash -Path $_.FullName -Algorithm SHA256).Hash.ToLower() - # the '*' before the packagename signifies it is a binary - "$hash *$packageName" - } - $checksums | Out-File -FilePath $OutputPath -Force - $fileContent = Get-Content -Path $OutputPath -Raw | Out-String - Write-Verbose -Verbose -Message $fileContent - displayName: Add sha256 hashes - - - task: PowerShell@2 - inputs: - targetType: inline - pwsh: true - script: | - Get-ChildItem $(Pipeline.Workspace) -recurse | Select-Object -ExpandProperty FullName - displayName: List all files in the workspace - - - task: PowerShell@2 - inputs: - targetType: inline - pwsh: true - script: | - Import-module '$(Pipeline.Workspace)/ToolArtifact/GitHubRelease.psm1' - $releaseVersion = '$(ReleaseTag)' -replace '^v','' - Write-Verbose -Verbose "Available modules: " - Get-Module | Write-Verbose -Verbose - - $filePath = Get-ChildItem -Path "$(Pipeline.Workspace)/CHANGELOG" -Filter '*.md' | Select-Object -First 1 -ExpandProperty FullName - - if (-not (Test-Path $filePath)) { - throw "$filePath not found" - } - - $changelog = Get-Content -Path $filePath - - $headingPattern = "^## \[\d+\.\d+\.\d+" - $headingStartLines = $changelog | Select-String -Pattern $headingPattern | Select-Object -ExpandProperty LineNumber - $startLine = $headingStartLines[0] - $endLine = $headingStartLines[1] - 1 - - $clContent = $changelog | Select-Object -Skip ($startLine-1) -First ($endLine - $startLine) | Out-String - - Write-Verbose -Verbose "Selected content: `n$clContent" - - Publish-ReleaseDraft -Tag '$(ReleaseTag)' -Name '$(ReleaseTag) Release of PowerShell' -Description $clContent -User PowerShell -Repository PowerShell -PackageFolder "$(Pipeline.Workspace)/GitHubPackages" -Token $(GitHubReleasePat) - displayName: Publish Release Draft 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-nuget.yml b/.pipelines/templates/release-publish-nuget.yml deleted file mode 100644 index 98249844d4c..00000000000 --- a/.pipelines/templates/release-publish-nuget.yml +++ /dev/null @@ -1,58 +0,0 @@ -parameters: - - name: skipPublish - default: false - type: boolean - -jobs: -- job: NuGetPublish - displayName: Publish to NuGet - condition: succeeded() - pool: - type: release - os: windows - templateContext: - inputs: - - input: pipelineArtifact - artifactName: drop_setReleaseTagAndUploadTools_SetTagAndTools - - input: pipelineArtifact - pipeline: PSPackagesOfficial - artifactName: drop_upload_upload_packages - variables: - - template: ./variable/release-shared.yml@self - parameters: - VERSION: $[ stageDependencies.setReleaseTagAndUploadTools.SetTagAndTools.outputs['OutputVersion.Version'] ] - - steps: - - template: release-install-pwsh.yml - - - pwsh: | - Write-Verbose -Verbose "Version: $(Version)" - Get-ChildItem Env: | Out-String -width 9999 -Stream | write-Verbose -Verbose - displayName: 'Capture Environment Variables' - - - pwsh: | - #Exclude all global tool packages. Their names start with 'PowerShell.' - $null = New-Item -ItemType Directory -Path "$(Pipeline.Workspace)/release" - Copy-Item "$(Pipeline.Workspace)/NuGetPackages/*.nupkg" -Destination "$(Pipeline.Workspace)/release" -Exclude "PowerShell.*.nupkg" -Force -Verbose - - $releaseVersion = '$(Version)' - $globalToolPath = "$(Pipeline.Workspace)/NuGetPackages/PowerShell.$releaseVersion.nupkg" - - if ($releaseVersion -notlike '*-*') { - # Copy the global tool package for stable releases - Copy-Item $globalToolPath -Destination "$(Pipeline.Workspace)/release" - } - - Write-Verbose -Verbose "The .nupkgs below will be pushed:" - Get-ChildItem "$(Pipeline.Workspace)/release" -recurse - displayName: Download and capture nupkgs - condition: and(ne('${{ parameters.skipPublish }}', 'false'), succeeded()) - - - task: NuGetCommand@2 - displayName: 'NuGet push' - condition: and(ne('${{ parameters.skipPublish }}', 'false'), succeeded()) - inputs: - command: push - packagesToPush: '$(Pipeline.Workspace)/release/*.nupkg' - nuGetFeedType: external - publishFeedCredentials: PowerShellNuGetOrgPush 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 8ddaa8328a5..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' @@ -33,7 +31,7 @@ jobs: env: ob_restore_phase: true # This ensures checkout is done at the beginning of the restore phase - - template: release-SetReleaseTagAndContainerName.yml + - template: release-SetReleaseTagandContainerName.yml - pwsh: | Get-ChildItem Env: | Out-String -width 9999 -Stream | write-Verbose -Verbose diff --git a/.pipelines/templates/release-upload-buildinfo.yml b/.pipelines/templates/release-upload-buildinfo.yml index 8d59078b905..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' @@ -38,7 +36,7 @@ jobs: env: ob_restore_phase: true # This ensures checkout is done at the beginning of the restore phase - - template: release-SetReleaseTagAndContainerName.yml + - template: release-SetReleaseTagandContainerName.yml - pwsh: | Get-ChildItem Env: | Out-String -width 9999 -Stream | write-Verbose -Verbose @@ -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 983d6690036..5953366ffd7 100644 --- a/.pipelines/templates/release-validate-packagenames.yml +++ b/.pipelines/templates/release-validate-packagenames.yml @@ -16,7 +16,7 @@ jobs: - checkout: self clean: true - - template: release-SetReleaseTagAndContainerName.yml + - template: release-SetReleaseTagandContainerName.yml - pwsh: | Get-ChildItem ENV: | Out-String -width 9999 -Stream | write-Verbose -Verbose @@ -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 3bcceeb1af7..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' @@ -25,6 +23,7 @@ jobs: value: $(Build.SourcesDirectory)\PowerShell\.config\suppress.json - name: ob_sdl_codeql_compiled_enabled value: false + - group: 'Azure Blob variable group' steps: - checkout: self @@ -35,8 +34,9 @@ jobs: - template: /.pipelines/templates/SetVersionVariables.yml@self parameters: ReleaseTagVar: $(ReleaseTagVar) - CreateJson: yes - UseJson: no + CreateJson: no + + - template: /.pipelines/templates/release-SetReleaseTagandContainerName.yml@self - template: /.pipelines/templates/cloneToOfficialPath.yml@self @@ -149,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 @@ -185,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 @@ -197,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 @@ -231,23 +225,32 @@ jobs: targetPath: '$(Build.ArtifactStagingDirectory)/downloads' displayName: Download macos x64 packages + - task: DownloadPipelineArtifact@2 + inputs: + buildType: 'current' + artifact: drop_msixbundle_CreateMSIXBundle + itemPattern: | + **/*.msixbundle + targetPath: '$(Build.ArtifactStagingDirectory)/downloads' + displayName: Download MSIXBundle + - pwsh: | Get-ChildItem '$(Build.ArtifactStagingDirectory)/downloads' | Select-Object -ExpandProperty FullName displayName: 'Capture downloads' - 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 @@ -386,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 @@ -397,3 +400,26 @@ jobs: Write-Host "File $blobName uploaded to $containerName container." Move-Item -Path $_.FullName -Destination $uploadedDirectory -Force -Verbose } + + $msixbundleFiles = Get-ChildItem -Path $downloadsDirectory -Filter "*.msixbundle" + + $containerName = '$(OutputVersion.AzureVersion)-private' + $storageAccount = '$(StorageAccount)' + + $storageContext = New-AzStorageContext -StorageAccountName $storageAccount -UseConnectedAccount + + if ($msixbundleFiles) { + $bundleFile = $msixbundleFiles[0].FullName + $blobName = $msixbundleFiles[0].Name + + $existing = Get-AzStorageBlob -Container $containerName -Blob $blobName -Context $storageContext -ErrorAction Ignore + if ($existing) { + Write-Verbose -Verbose "MSIX bundle already exists at '$storageAccount/$containerName/$blobName', removing first." + $existing | Remove-AzStorageBlob -ErrorAction Stop -Verbose + } + + Write-Verbose -Verbose "Uploading $bundleFile to $containerName/$blobName" + Set-AzStorageBlobContent -File $bundleFile -Container $containerName -Blob $blobName -Context $storageContext -Force + } else { + throw "MSIXBundle not found in $downloadsDirectory" + } 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 f5cebac1316..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' 'net9.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\net9.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\net9.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' 'net9.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/.prettierrc b/.prettierrc new file mode 100644 index 00000000000..222861c3415 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +{ + "tabWidth": 2, + "useTabs": false +} 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 338821e37dd..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 @@ -67,7 +66,7 @@ stages: jobs: - template: templates/ci-build.yml parameters: - pool: ubuntu-20.04 + pool: ubuntu-latest jobName: linux_build displayName: linux Build @@ -77,4 +76,4 @@ stages: jobs: - template: linux/templates/packaging.yml parameters: - pool: ubuntu-20.04 + pool: ubuntu-latest 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 1a7d64868d0..eb506a7ddbc 100644 --- a/CHANGELOG/7.4.md +++ b/CHANGELOG/7.4.md @@ -1,5 +1,294 @@ # 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 + +- Move .NET method invocation logging to after the needed type conversion is done for method arguments (#25568) + +### Build and Packaging Improvements + +
+ + + +

Update .NET SDK to 8.0.411

+ +
+ +
    +
  • Correct Capitalization Referencing Templates (#25672)
  • +
  • Manually update SqlClient in TestService
  • +
  • Update cgmanifest
  • +
  • Update package references
  • +
  • Update .NET SDK to latest version
  • +
  • Change linux packaging tests to ubuntu latest (#25640)
  • +
+ +
+ +### Documentation and Help Content + +- Update Third Party Notices (#25524, #25659) + +[7.4.11]: https://github.com/PowerShell/PowerShell/compare/v7.4.10...v7.4.11 + + +## [7.4.10] + +### Engine Updates and Fixes + +- Fallback to AppLocker after `WldpCanExecuteFile` (#25229) + +### Code Cleanup + +
+ +
    +
  • Remove obsolete template from Windows Packaging CI (#25405)
  • +
  • Cleanup old release pipelines (#25404)
  • +
+ +
+ +### Tools + +- Do not run labels workflow in the internal repository (#25411) + +### Build and Packaging Improvements + +
+ + + +

Update .NET SDK to 8.0.408

+ +
+ +
    +
  • Update branch for release (#25518)
  • +
  • Move MSIXBundle to Packages and Release to GitHub (#25516)
  • +
  • Add CodeQL suppressions for PowerShell intended behavior (#25376)
  • +
  • Enhance path filters action to set outputs for all changes when not a PR (#25378)
  • +
  • Fix Merge Errors from #25401 and Internal 33077 (#25478)
  • +
  • Fix MSIX artifact upload, vPack template, changelog hashes, git tag command (#25476)
  • +
  • Fix Conditional Parameter to Skip NuGet Publish (#25475)
  • +
  • Use new variables template for vPack (#25474)
  • +
  • Add Windows Store Signing to MSIX bundle (#25472)
  • +
  • Update test result processing to use NUnitXml format and enhance logging for better clarity (#25471)
  • +
  • Fix the expected path of .NET after using UseDotnet 2 task to install (#25470)
  • +
  • Update Microsoft.PowerShell.PSResourceGet to 1.1.0 (#25469)
  • +
  • Combine GitHub and Nuget Release Stage (#25473)
  • +
  • Make GitHub Workflows work in the internal mirror (#25409)
  • +
  • Add default .NET install path for SDK validation (#25339)
  • +
  • Update APIScan to use new symbols server (#25400)
  • +
  • Use GitHubReleaseTask (#25401)
  • +
  • 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-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)
  • +
  • Update package pipeline windows image version (#25192)
  • +
+ +
+ +[7.4.10]: https://github.com/PowerShell/PowerShell/compare/v7.4.9...v7.4.10 + ## [7.4.9] ### Notes @@ -155,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)
  • @@ -758,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)
  • @@ -766,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)
  • @@ -927,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 702c60346e7..b4f173bcff6 100644 --- a/CHANGELOG/7.5.md +++ b/CHANGELOG/7.5.md @@ -1,5 +1,334 @@ # 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 + +- Move .NET method invocation logging to after the needed type conversion is done for method arguments (#25357) + +### General Cmdlet Updates and Fixes + +- 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 filename suggestion (#25330) +- Fix `PSMethodInvocationConstraints.GetHashCode` method (#25306) (Thanks @crazyjncsu!) + +### Build and Packaging Improvements + +
    + + + +

    Update to .NET SDK 9.0.301

    + +
    + +
      +
    • Correct Capitalization Referencing Templates (#25673)
    • +
    • Publish .msixbundle package as a VPack (#25621)
    • +
    • Update ThirdPartyNotices for v7.5.2 (#25658)
    • +
    • Manually update SqlClient in TestService
    • +
    • Update cgmanifest
    • +
    • Update package references
    • +
    • Update .NET SDK to latest version
    • +
    • Change linux packaging tests to ubuntu latest (#25639)
    • +
    • Fix MSIX artifact upload, vPack template, changelog hashes, git tag command (#25633)
    • +
    • Move MSIXBundle to Packages and Release to GitHub (#25517)
    • +
    • Use new variables template for vPack (#25435)
    • +
    + +
    + +[7.5.2]: https://github.com/PowerShell/PowerShell/compare/v7.5.1...v7.5.2 + +## [7.5.1] + +### Engine Updates and Fixes + +- Fallback to AppLocker after `WldpCanExecuteFile` (#25305) + +### Code Cleanup + +
    + +
      +
    • Cleanup old release pipelines (#25236)
    • +
    + +
    + +### Tools + +- Do not run labels workflow in the internal repository (#25343) +- Update `CODEOWNERS` (#25321) +- Check GitHub token availability for `Get-Changelog` (#25328) +- Update PowerShell team members in `releaseTools.psm1` (#25302) + +### Build and Packaging Improvements + +
    + + + +

    Update to .NET SDK 9.0.203

    + +
    + +
      +
    • Finish 7.5.0 release (#24855)
    • +
    • Add CodeQL suppressions for PowerShell intended behavior (#25375)
    • +
    • Update to .NET SDK 9.0.203 (#25373)
    • +
    • Switch to ubuntu-lastest for CI (#25374)
    • +
    • Add default .NET install path for SDK validation (#25338)
    • +
    • Combine GitHub and Nuget Release Stage (#25371)
    • +
    • Add Windows Store Signing to MSIX bundle (#25370)
    • +
    • Update test result processing to use NUnitXml format and enhance logging for better clarity (#25344)
    • +
    • Fix MSIX stage in release pipeline (#25345)
    • +
    • Make GitHub Workflows work in the internal mirror (#25342)
    • +
    • Update security extensions (#25322)
    • +
    • Disable SBOM generation on set variables job in release build (#25340)
    • +
    • Update GitHub Actions to work in private GitHub repo (#25332)
    • +
    • Revert "Cleanup old release pipelines (#25201)" (#25335)
    • +
    • Remove call to NuGet (#25334)
    • +
    • Simplify PR Template (#25333)
    • +
    • Update package pipeline windows image version (#25331)
    • +
    • Skip additional packages when generating component manifest (#25329)
    • +
    • Only build Linux for packaging changes (#25326)
    • +
    • Make Component Manifest Updater use neutral target in addition to RID target (#25325)
    • +
    • Remove Az module installs and AzureRM uninstalls in pipeline (#25327)
    • +
    • Make sure the vPack pipeline does not produce an empty package (#25320)
    • +
    • Add *.props and sort path filters for windows CI (#25316)
    • +
    • Fix V-Pack download package name (#25314)
    • +
    • Update path filters for Windows CI (#25312)
    • +
    • Give the pipeline runs meaningful names (#25309)
    • +
    • Migrate MacOS Signing to OneBranch (#25304)
    • +
    • Add UseDotnet task for installing dotnet (#25281)
    • +
    • Remove obsolete template from Windows Packaging CI (#25237)
    • +
    • Add setup dotnet action to the build composite action (#25235)
    • +
    • Add GitHub Actions workflow to verify PR labels (#25159)
    • +
    • Update branch for release - Transitive - true - minor (#24994)
    • +
    • Fix GitHub Action filter overmatching (#24958)
    • +
    • Fix release branch filters (#24959)
    • +
    • Convert powershell/PowerShell-CI-macos to GitHub Actions (#24954)
    • +
    • Convert powershell/PowerShell-CI-linux to GitHub Actions (#24946)
    • +
    • Convert powershell/PowerShell-Windows-CI to GitHub Actions (#24931)
    • +
    • PMC parse state correctly from update command's response (#24859)
    • +
    • Add EV2 support for publishing PowerShell packages to PMC (#24856)
    • +
    + +
    + +[7.5.1]: https://github.com/PowerShell/PowerShell/compare/v7.5.0...v7.5.1 + ## [7.5.0] ### Build and Packaging Improvements 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 ecc87bf031c..8f23d37c9bb 100644 --- a/CHANGELOG/preview.md +++ b/CHANGELOG/preview.md @@ -1,199 +1,61 @@ # Preview Changelog -## [7.6.0-preview.3] +## [7.7.0-preview.1] ### Breaking Changes -- Remove trailing space from event source name (#24192) (Thanks @MartinGC94!) +- 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!) -### 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!) +### Engine Updates and Fixes -### 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!) +- 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 -- 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) +- 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 @@ -202,27 +64,81 @@

    We thank the following contributors!

    -

    @eltociear, @JayBazuzi

    +

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

      -
    • Fix typos in ShowModuleControl.xaml.cs (#24248) (Thanks @eltociear!)
    • -
    • Fix a typo in the build doc (#24172) (Thanks @JayBazuzi!)
    • +
    • 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 -- 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) +- 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 -- Fix cleanup in PSResourceGet test (#24339) +- 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 @@ -231,91 +147,106 @@

    We thank the following contributors!

    -

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

    +

    @powercode, @kasperk81, @xtqqczze

      -
    • 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)
    • +
    • 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 -- 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 9ca72b0577c..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.2.25164.34", + "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 1fed2966637..a7b31c475f8 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Welcome to the PowerShell GitHub Community! 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. -[logo]: https://raw.githubusercontent.com/PowerShell/PowerShell/master/assets/ps_black_64.svg?sanitize=true +[logo]: assets/ps_black_64.svg?sanitize=true ## Windows PowerShell vs. PowerShell 7+ @@ -56,39 +56,33 @@ 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) -## Building the Repository +## Developing and Contributing -| Linux | Windows | macOS | -|--------------------------|----------------------------|------------------------| -| [Instructions][bd-linux] | [Instructions][bd-windows] | [Instructions][bd-macOS] | +Want to contribute to PowerShell? Please start with the [Contribution Guide][] to learn how to develop and contribute. -If you have any problems building, consult the developer [FAQ][]. +If you are developing .NET Core C# applications targeting PowerShell Core, [check out our FAQ][] to learn more about the PowerShell SDK NuGet package. -### Build status of nightly builds +Also, make sure to check out our [PowerShell-RFC repository](https://github.com/powershell/powershell-rfc) for request-for-comments (RFC) documents to submit and give comments on proposed and future designs. + +[Contribution Guide]: .github/CONTRIBUTING.md +[check out our FAQ]: docs/FAQ.md#where-do-i-get-the-powershell-core-sdk-package -| 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] | +## Building PowerShell -[bd-linux]: https://github.com/PowerShell/PowerShell/tree/master/docs/building/linux.md -[bd-windows]: https://github.com/PowerShell/PowerShell/tree/master/docs/building/windows-core.md -[bd-macOS]: https://github.com/PowerShell/PowerShell/tree/master/docs/building/macos.md +| Linux | Windows | macOS | +|--------------------------|----------------------------|------------------------| +| [Instructions][bd-linux] | [Instructions][bd-windows] | [Instructions][bd-macOS] | -[FAQ]: https://github.com/PowerShell/PowerShell/tree/master/docs/FAQ.md +If you have any problems building PowerShell, please start by consulting the developer [FAQ]. -[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 +[bd-linux]: docs/building/linux.md +[bd-windows]: docs/building/windows-core.md +[bd-macOS]: docs/building/macos.md +[FAQ]: docs/FAQ.md ## Downloading the Source Code @@ -100,16 +94,6 @@ git clone https://github.com/PowerShell/PowerShell.git For more information, see [working with the PowerShell repository](https://github.com/PowerShell/PowerShell/tree/master/docs/git). -## Developing and Contributing - -Please look into the [Contribution Guide][] to know how to develop and contribute. -If you are developing .NET Core C# applications targeting PowerShell Core, [check out our FAQ][] to learn more about the PowerShell SDK NuGet package. - -Also, make sure to check out our [PowerShell-RFC repository](https://github.com/powershell/powershell-rfc) for request-for-comments (RFC) documents to submit and give comments on proposed and future designs. - -[Contribution Guide]: https://github.com/PowerShell/PowerShell/blob/master/.github/CONTRIBUTING.md -[check out our FAQ]: https://github.com/PowerShell/PowerShell/tree/master/docs/FAQ.md#where-do-i-get-the-powershell-core-sdk-package - ## Support For support, see the [Support Section][]. @@ -122,7 +106,10 @@ PowerShell is licensed under the [MIT license][]. [MIT license]: https://github.com/PowerShell/PowerShell/tree/master/LICENSE.txt -### Windows Docker Files and Images +### Docker Containers + +> [!Important] +> The PowerShell container images are now [maintained by the .NET team](https://github.com/PowerShell/Announcements/issues/75). The containers at `mcr.microsoft.com/powershell` are currently not maintained. License: By requesting and using the Container OS Image for Windows containers, you acknowledge, understand, and consent to the Supplemental License Terms available on [Microsoft Artifact Registry][mcr]. diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt index f45bc2f3c2b..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.3 - MIT +JsonSchema.Net 7.4.0 - MIT Copyright (c) .NET Foundation and Contributors @@ -151,7 +151,7 @@ SOFTWARE. --------------------------------------------------------- -Microsoft.ApplicationInsights 2.22.0 - MIT +Microsoft.ApplicationInsights 2.23.0 - MIT (c) Microsoft Corporation @@ -170,7 +170,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -Microsoft.Bcl.AsyncInterfaces 9.0.2 - 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.12.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.12.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.2 - MIT +Microsoft.Extensions.ObjectPool 10.0.3 - MIT Copyright Jorn Zaefferer @@ -375,7 +359,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -Microsoft.Security.Extensions 1.3.0 - MIT +Microsoft.Security.Extensions 1.4.0 - MIT (c) Microsoft Corporation @@ -395,79 +379,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -Microsoft.Win32.Registry 5.0.0 - MIT - - -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -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) 1998 Microsoft. To -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 2012-2014, Yann Collet -Copyright (c) 1991-2020 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, 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 -Portions (c) International Organization -Copyright (c) 2004-2006 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 -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler -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. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -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) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass. To - -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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Win32.Registry.AccessControl 9.0.2 - MIT +Microsoft.Win32.Registry.AccessControl 10.0.3 - MIT Copyright (c) 2021 @@ -490,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 @@ -511,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. @@ -528,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.2 - MIT +Microsoft.Win32.SystemEvents 10.0.3 - MIT Copyright (c) 2021 @@ -580,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 @@ -601,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. @@ -618,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.2 - MIT +Microsoft.Windows.Compatibility 10.0.3 - MIT (c) Microsoft Corporation @@ -666,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 @@ -700,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.2 - MIT +runtime.android-arm.runtime.native.System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -723,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 @@ -744,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. @@ -761,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.2 - MIT +runtime.android-arm64.runtime.native.System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -813,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 @@ -834,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. @@ -851,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.2 - MIT +runtime.android-x64.runtime.native.System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -903,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 @@ -924,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. @@ -941,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.2 - MIT +runtime.android-x86.runtime.native.System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -993,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 @@ -1014,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. @@ -1031,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.2 - MIT +runtime.linux-arm.runtime.native.System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -1083,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 @@ -1104,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. @@ -1121,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) +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-arm64.runtime.native.System.IO.Ports 9.0.2 - MIT +runtime.linux-arm64.runtime.native.System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -1173,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 @@ -1194,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. @@ -1211,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.2 - MIT +runtime.linux-bionic-arm64.runtime.native.System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -1263,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 @@ -1284,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. @@ -1301,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) - -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-x64.runtime.native.System.IO.Ports 9.0.2 - MIT +runtime.linux-bionic-x64.runtime.native.System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -1353,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 @@ -1374,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. @@ -1391,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.2 - MIT +runtime.linux-musl-arm.runtime.native.System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -1443,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 @@ -1464,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. @@ -1481,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.2 - MIT +runtime.linux-musl-arm64.runtime.native.System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -1533,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 @@ -1554,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. @@ -1571,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.2 - MIT +runtime.linux-musl-x64.runtime.native.System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -1623,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 @@ -1644,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. @@ -1661,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.2 - MIT +runtime.linux-x64.runtime.native.System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -1713,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 @@ -1734,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. @@ -1751,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.2 - MIT +runtime.maccatalyst-arm64.runtime.native.System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -1803,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 @@ -1824,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. @@ -1841,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.2 - MIT +runtime.maccatalyst-x64.runtime.native.System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -1893,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 @@ -1914,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. @@ -1931,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. --------------------------------------------------------- @@ -2005,7 +1661,7 @@ SOFTWARE. --------------------------------------------------------- -runtime.native.System.IO.Ports 9.0.2 - MIT +runtime.native.System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -2028,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 @@ -2049,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. @@ -2066,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) +MIT License -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: +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.2 - MIT +runtime.osx-arm64.runtime.native.System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -2118,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 @@ -2139,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. @@ -2156,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.2 - MIT +runtime.osx-x64.runtime.native.System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -2208,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 @@ -2229,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. @@ -2246,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) - -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.CodeDom 9.0.2 - MIT +System.CodeDom 10.0.3 - MIT Copyright (c) 2021 @@ -2298,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 @@ -2319,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. @@ -2336,48 +1943,36 @@ 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.Collections.Immutable 8.0.0 - MIT +System.ComponentModel.Composition 10.0.3 - 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) 1998 Microsoft. To +Copyright (c) 2015 Andrew Gallant Copyright (c) 2022, Wojciech Mula Copyright (c) 2017 Yoshifumi Kawai Copyright (c) 2022, Geoff Langdale @@ -2385,73 +1980,58 @@ 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 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) 2021 csFastFloat authors +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 -Portions (c) International Organization 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) 1995-2022 Jean-loup Gailly and Mark Adler 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. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers +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 -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.2 - MIT +System.ComponentModel.Composition.Registration 10.0.3 - MIT Copyright (c) 2021 @@ -2474,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 @@ -2495,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. @@ -2512,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.ComponentModel.Composition.Registration 9.0.2 - MIT +System.Configuration.ConfigurationManager 10.0.3 - MIT Copyright (c) 2021 @@ -2564,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 @@ -2585,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. @@ -2602,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.Configuration.ConfigurationManager 9.0.2 - MIT +System.Data.Odbc 10.0.3 - MIT Copyright (c) 2021 @@ -2654,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 @@ -2675,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. @@ -2692,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.Odbc 9.0.2 - MIT +System.Data.OleDb 10.0.3 - MIT Copyright (c) 2021 @@ -2744,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 @@ -2765,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. @@ -2782,59 +2313,63 @@ 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.2 - MIT +System.Data.SqlClient 4.9.0 - 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 + +MIT License + +Copyright (c) + +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.Diagnostics.EventLog 10.0.3 - 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) 1991-2024 Unicode, Inc. Copyright (c) 2013-2017, Alfred Klomp Copyright (c) 2018 Nemanja Mijailovic Copyright 2012 the V8 project authors @@ -2855,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. @@ -2872,40 +2406,6 @@ 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.Data.SqlClient 4.9.0 - MIT - - -(c) Microsoft Corporation - MIT License Copyright (c) @@ -2920,7 +2420,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -System.Diagnostics.DiagnosticSource 9.0.2 - MIT +System.Diagnostics.PerformanceCounter 10.0.3 - MIT Copyright (c) 2021 @@ -2943,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 @@ -2964,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. @@ -2981,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.Diagnostics.EventLog 9.0.2 - MIT +System.DirectoryServices 10.0.3 - MIT Copyright (c) 2021 @@ -3033,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 @@ -3054,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. @@ -3071,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.Diagnostics.PerformanceCounter 9.0.2 - MIT +System.DirectoryServices.AccountManagement 10.0.3 - MIT Copyright (c) 2021 @@ -3123,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 @@ -3144,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. @@ -3161,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) - -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.2 - MIT +System.DirectoryServices.Protocols 10.0.3 - MIT Copyright (c) 2021 @@ -3213,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 @@ -3234,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. @@ -3251,6 +2702,27 @@ 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 +MIT License + +Copyright (c) + +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.Drawing.Common 10.0.3 - MIT + + +(c) Microsoft Corporation +Copyright (c) Sven Groot (Ookii.org) 2009 +Copyright (c) .NET Foundation and Contributors + The MIT License (MIT) Copyright (c) .NET Foundation and Contributors @@ -3275,12 +2747,11 @@ 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.2 - MIT +System.IO.Packaging 10.0.3 - MIT Copyright (c) 2021 @@ -3303,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 @@ -3324,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. @@ -3341,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.DirectoryServices.Protocols 9.0.2 - MIT +System.IO.Ports 10.0.3 - MIT Copyright (c) 2021 @@ -3393,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 @@ -3414,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. @@ -3431,71 +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. - -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.Drawing.Common 9.0.2 - MIT - - -(c) Microsoft Corporation -Copyright (c) Sven Groot (Ookii.org) 2009 -Copyright (c) .NET Foundation and Contributors - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors +MIT License -All rights reserved. +Copyright (c) -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 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 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.Packaging 9.0.2 - MIT +System.Management 10.0.3 - MIT Copyright (c) 2021 @@ -3518,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 @@ -3539,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. @@ -3556,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) +MIT License -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: +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.2 - MIT +System.Net.Http.WinHttpHandler 10.0.3 - MIT Copyright (c) 2021 @@ -3608,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 @@ -3629,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. @@ -3646,36 +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. +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.2 - MIT +System.Reflection.Context 10.0.3 - MIT Copyright (c) 2021 @@ -3698,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 @@ -3719,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. @@ -3736,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.Net.Http.WinHttpHandler 9.0.2 - MIT +System.Runtime.Caching 10.0.3 - MIT Copyright (c) 2021 @@ -3788,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 @@ -3809,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. @@ -3826,121 +3181,95 @@ 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.Numerics.Vectors 4.5.0 - MIT +System.Security.Cryptography.Pkcs 10.0.3 - MIT -(c) 2023 GitHub, Inc. +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) 1991-2017 Unicode, Inc. +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-2024 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 -Portions (c) International Organization +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 -Copyright (c) .NET Foundation and Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +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) 1995-2017 Jean-loup Gailly and Mark Adler Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers - -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 +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 -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.2 - MIT +System.Security.Cryptography.ProtectedData 10.0.3 - MIT Copyright (c) 2021 @@ -3963,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 @@ -3984,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. @@ -4001,1040 +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. - -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.Reflection.DispatchProxy 4.7.1 - MIT +MIT License +Copyright (c) -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. -Copyright (c) 2011, Google Inc. -(c) 1997-2005 Sean Eron Anderson. -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) .NET Foundation Contributors -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS +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 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.Reflection.Metadata 8.0.0 - MIT - - -Copyright (c) Six Labors -Gets the Copyright Table -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -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) 1998 Microsoft. To -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 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) 2021 csFastFloat authors -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Portions (c) International Organization -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 -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) 1995-2022 Jean-loup Gailly and Mark Adler -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. -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 - -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.Runtime.Caching 9.0.2 - 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 - -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.Security.AccessControl 6.0.1 - MIT - - -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -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) 1998 Microsoft. To -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2005-2020 Rich Felker -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 2012-2014, Yann Collet -Copyright (c) 1991-2020 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, 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 -Portions (c) International Organization -Copyright (c) 2004-2006 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 -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -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. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -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) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass. To - -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.Security.Cryptography.Pkcs 9.0.2 - 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 - -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.Security.Cryptography.ProtectedData 9.0.2 - 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 - -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.Security.Cryptography.Xml 9.0.2 - 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 - -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.Security.Permissions 9.0.2 - 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 - -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.Security.Principal.Windows 5.0.0 - MIT - - -(c) Microsoft Corporation. -Copyright (c) Andrew Arnott -Copyright 2018 Daniel Lemire -Copyright 2012 the V8 project -Copyright (c) .NET Foundation. -Copyright (c) 2011, Google Inc. -Copyright (c) 1998 Microsoft. To -(c) 1997-2005 Sean Eron Anderson. -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 2012-2014, Yann Collet -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2018 Alexander Chermyanin -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. -Copyright (c) The Internet Society 1997. -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) .NET Foundation Contributors -Copyright (c) The Internet Society (2003). -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler -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. -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS -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) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass. To - -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. - -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.NetTcp 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.Primitives 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.Security 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.Syndication 9.0.2 - 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 - -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. +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.ServiceProcess.ServiceController 9.0.2 - MIT +System.Security.Cryptography.Xml 10.0.3 - MIT Copyright (c) 2021 @@ -5057,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 @@ -5078,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. @@ -5095,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.Speech 9.0.2 - MIT +System.Security.Permissions 10.0.3 - MIT Copyright (c) 2021 @@ -5147,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 @@ -5168,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. @@ -5185,6 +3477,135 @@ 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 +MIT License + +Copyright (c) + +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 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) Provided + +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.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) Provided + +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.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) Provided + +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.Primitives 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) Provided + The MIT License (MIT) Copyright (c) .NET Foundation and Contributors @@ -5214,7 +3635,7 @@ SOFTWARE. --------------------------------------------------------- -System.Text.Encoding.CodePages 9.0.2 - MIT +System.ServiceModel.Syndication 10.0.3 - MIT Copyright (c) 2021 @@ -5237,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 @@ -5258,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. @@ -5275,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.Text.Encodings.Web 9.0.2 - MIT +System.ServiceProcess.ServiceController 10.0.3 - MIT Copyright (c) 2021 @@ -5327,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 @@ -5348,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. @@ -5365,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.2 - MIT +System.Speech 10.0.3 - MIT Copyright (c) 2021 @@ -5417,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 @@ -5438,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. @@ -5455,38 +3843,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. +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 4.10.0 - MIT +System.Web.Services.Description 8.1.2 - 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) Provided The MIT License (MIT) @@ -5517,7 +3893,7 @@ SOFTWARE. --------------------------------------------------------- -System.Windows.Extensions 9.0.2 - MIT +System.Windows.Extensions 10.0.3 - MIT Copyright (c) 2021 @@ -5540,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 @@ -5561,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. @@ -5578,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. --------------------------------------------------------- @@ -5797,3 +4157,4 @@ 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 c646bcdf94b..dfcd95935d9 100644 --- a/assets/AppxManifest.xml +++ b/assets/AppxManifest.xml @@ -43,9 +43,11 @@ + + 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 6a809d4231d..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]) { @@ -179,6 +232,8 @@ function Get-EnvironmentInformation $environment += @{'IsUbuntu16' = $environment.IsUbuntu -and $LinuxInfo.VERSION_ID -match '16.04'} $environment += @{'IsUbuntu18' = $environment.IsUbuntu -and $LinuxInfo.VERSION_ID -match '18.04'} $environment += @{'IsUbuntu20' = $environment.IsUbuntu -and $LinuxInfo.VERSION_ID -match '20.04'} + $environment += @{'IsUbuntu22' = $environment.IsUbuntu -and $LinuxInfo.VERSION_ID -match '22.04'} + $environment += @{'IsUbuntu24' = $environment.IsUbuntu -and $LinuxInfo.VERSION_ID -match '24.04'} $environment += @{'IsCentOS' = $LinuxInfo.ID -match 'centos' -and $LinuxInfo.VERSION_ID -match '7'} $environment += @{'IsFedora' = $LinuxInfo.ID -match 'fedora' -and $LinuxInfo.VERSION_ID -ge 24} $environment += @{'IsOpenSUSE' = $LinuxInfo.ID -match 'opensuse'} @@ -191,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 @@ -279,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 @@ -351,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) { @@ -383,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 @@ -391,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 } } @@ -490,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 @@ -523,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 @@ -553,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 @@ -607,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) { @@ -614,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(';') } @@ -627,6 +747,7 @@ Fix steps: } finally { Pop-Location } + Write-LogGroupEnd -Title "Publish Reference Assemblies" if ($ReleaseTag) { $psVersion = $ReleaseTag @@ -669,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*") { @@ -718,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 @@ -737,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')] @@ -788,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 @@ -827,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()] @@ -941,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] @@ -957,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")) @@ -965,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 @@ -979,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 @@ -1126,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] @@ -1141,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 @@ -1150,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 ) @@ -1163,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 = @() @@ -1229,6 +1496,14 @@ 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" $publishPath = "${PSScriptRoot}/test/tools/Modules" @@ -1253,15 +1528,30 @@ function Publish-CustomConnectionTestModule finally { Pop-Location } + + Write-LogGroupEnd -Title "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] $runtime ) + Write-LogGroupStart -Title "Publish-PSTestTools" Find-Dotnet $tools = @( @@ -1333,9 +1623,20 @@ function Publish-PSTestTools { # Publish the Microsoft.PowerShell.NamedPipeConnection module Publish-CustomConnectionTestModule + Write-LogGroupEnd -Title "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 @@ -1348,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)] @@ -1397,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 @@ -1713,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] @@ -1773,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 @@ -1783,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" } @@ -1793,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)] @@ -1820,17 +2209,105 @@ function Show-PSPesterError throw 'Unknown Show-PSPester parameter set' } - Write-Log -isError -message ("Description: " + $description) - Write-Log -isError -message ("Name: " + $name) - Write-Log -isError -message "message:" - Write-Log -isError -message $message - Write-Log -isError -message "stack-trace:" - Write-Log -isError -message $stack_trace + # Empty line at the end is intentional formatting + Write-Log -isError -message @" +Description: $description +Name: $name +message: +$message +stack-trace: +$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()] @@ -1865,13 +2342,17 @@ function Test-XUnitTestResults $message = $failure.failure.message $stack_trace = $failure.failure.'stack-trace' - Write-Log -isError -message ("Description: " + $description) - Write-Log -isError -message ("Name: " + $name) - Write-Log -isError -message "message:" - Write-Log -isError -message $message - Write-Log -isError -message "stack-trace:" - Write-Log -isError -message $stack_trace - Write-Log -isError -message " " + # Empty line at the end is intentional formatting + Write-Log -isError -message @" + Description: $description + Name: $name + message: + $message + stack-trace: + $stack_trace + +"@ + } throw "$($results.assemblies.assembly.failed) tests failed" @@ -1882,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')] @@ -1907,7 +2405,8 @@ function Test-PSPesterResults $x = [xml](Get-Content -Raw $testResultsFile) if ([int]$x.'test-results'.failures -gt 0) { - Write-Log -isError -message "TEST FAILURES" + Write-LogGroupStart -Title 'TEST FAILURES' + # switch between methods, SelectNode is not available on dotnet core if ( "System.Xml.XmlDocumentXPathExtensions" -as [Type] ) { @@ -1921,6 +2420,8 @@ function Test-PSPesterResults { Show-PSPesterError -testFailure $testfail } + + Write-LogGroupEnd -Title 'TEST FAILURES' throw "$($x.'test-results'.failures) tests in $TestArea failed" } } @@ -1941,11 +2442,12 @@ function Test-PSPesterResults } elseif ($ResultObject.FailedCount -gt 0) { - Write-Log -isError -message 'TEST FAILURES' + Write-LogGroupStart -Title 'TEST FAILURES' $ResultObject.TestResult | Where-Object {$_.Passed -eq $false} | ForEach-Object { Show-PSPesterError -testFailureObject $_ } + Write-LogGroupEnd -Title 'TEST FAILURES' throw "$($ResultObject.FailedCount) tests in $TestArea failed" } @@ -1953,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 @@ -2012,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 @@ -2023,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, @@ -2035,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 @@ -2133,7 +2694,6 @@ function Install-Dotnet { $installArgs += @{ SkipNonVersionedFiles = $true } $installArgs | Out-String | Write-Verbose -Verbose - & ./$installScript @installArgs } else { @@ -2170,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, @@ -2230,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" ) @@ -2244,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 } @@ -2264,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 @@ -2288,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 @@ -2309,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" @@ -2348,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" @@ -2396,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)) { @@ -2407,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 @@ -2427,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 @@ -2451,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 = @(), @@ -2518,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 ( @@ -2528,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 } @@ -2567,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() @@ -2581,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 ) @@ -2611,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 + } + } } <# @@ -2663,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" @@ -2682,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)] @@ -2692,7 +3435,13 @@ function script:Write-Log ) if ($isError) { - Write-Host -Foreground Red $message + if ($env:GITHUB_WORKFLOW) { + # https://github.com/actions/toolkit/issues/193#issuecomment-605394935 + $escapedMessage = $message -replace "`n", "%0A" -replace "`r" + Write-Host "::error::${escapedMessage}" + } else { + Write-Host -Foreground Red $message + } } else { @@ -2701,7 +3450,104 @@ function script:Write-Log #reset colors for older package to at return to default after error message on a compilation error [console]::ResetColor() } + +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)] + [ValidateNotNullOrEmpty()] + [string[]] $Message, + [Parameter(Mandatory)] + [string] $Title + ) + + + Write-LogGroupStart -Title $Title + + foreach ($line in $Message) { + Write-Log -Message $line + } + + Write-LogGroupEnd -Title $Title +} + +$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)] + [string] $Title + ) + + if ($env:GITHUB_WORKFLOW) { + Write-Host "::group::${Title}" + } + else { + Write-Host -ForegroundColor $script:logGroupColor "=== BEGIN: $Title ===" + } +} + +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)] + [string] $Title + ) + + if ($env:GITHUB_WORKFLOW) { + Write-Host "::endgroup::" + } + else { + Write-Host -ForegroundColor $script:logGroupColor "==== END: $Title ====" + } +} + 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)) @@ -2717,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() @@ -2729,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)] @@ -2788,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)] @@ -2829,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)] @@ -2836,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 { @@ -3197,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()] @@ -3214,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] @@ -3246,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, @@ -3416,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 ) @@ -3442,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)] @@ -3518,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 @@ -3528,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')] @@ -3598,13 +4579,35 @@ function New-NugetConfigFile { $content += $newLine + $nugetConfigFooterTemplate Set-Content -Path (Join-Path $Destination 'nuget.config') -Value $content -Force + + # Set the nuget.config file to be skipped by git + push-location $Destination + try { + git update-index --skip-worktree (Join-Path $Destination 'nuget.config') + } finally { + pop-location + } } 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) { @@ -3619,25 +4622,75 @@ 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) { + Write-LogGroupEnd -Title "Set-CorrectLocale" return } $environment = Get-EnvironmentInformation - if ($environment.IsUbuntu -and $environment.IsUbuntu20) - { + if ($environment.IsUbuntu16 -or $environment.IsUbuntu18) { + Write-Verbose -Message "Don't set locale before Ubuntu 20" -Verbose + Write-LogGroupEnd -Title "Set-CorrectLocale" + Write-Locale + return + } + + if ($environment.IsUbuntu) { + Write-Log -Message "Setting locale to en_US.UTF-8" $env:LC_ALL = 'en_US.UTF-8' $env:LANG = 'en_US.UTF-8' sudo locale-gen $env:LANG - sudo update-locale + if ($environment.IsUbuntu20) { + Write-Log -Message "Updating locale for Ubuntu 20" + sudo update-locale + } else { + Write-Log -Message "Updating locale for Ubuntu 22 and newer" + sudo update-locale LANG=$env:LANG LC_ALL=$env:LC_ALL + } + } + + Write-LogGroupEnd -Title "Set-CorrectLocale" + Write-Locale + +} + +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 } # Output the locale to log it - locale + $localOutput = & locale + Write-LogGroup -Title "Capture Locale" -Message $localOutput } 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 @@ -3652,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) { @@ -3667,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 ) @@ -3701,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*' @@ -3733,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 @@ -3752,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 31289ff5281..20774695fcd 100644 --- a/docs/building/windows-core.md +++ b/docs/building/windows-core.md @@ -35,7 +35,7 @@ The `Start-PSBootstrap` function will automatically install it and add it to you ```powershell Import-Module ./build.psm1 -Start-PSBootstrap +Start-PSBootstrap -Scenario Dotnet ``` Or you can call `Install-Dotnet` directly: @@ -54,11 +54,15 @@ If you have any problems installing `dotnet`, please see their [documentation][c We maintain a [PowerShell module](../../build.psm1) with the function `Start-PSBuild` to build PowerShell. +We do not recommend using Visual Studio Dev Environment Terminal to build the source code. + ```powershell 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/community/working-group-definitions.md b/docs/community/working-group-definitions.md index f3c01442de4..277fc37f789 100644 --- a/docs/community/working-group-definitions.md +++ b/docs/community/working-group-definitions.md @@ -30,7 +30,7 @@ The PowerShell developer experience includes the **development of modules** (in as well as the experience of **hosting PowerShell and its APIs** in other applications and language runtimes. Special consideration should be given to topics like **backwards compatibility** with Windows PowerShell (e.g. with **PowerShell Standard**) and **integration with related developer tools** -(e.g. .NET CLI or the PowerShell extension for VS Code). +(e.g. .NET CLI or the PowerShell extension for Visual Studio Code). ### Members 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 c30f249d4b2..d31f5220d83 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "10.0.100-preview.2.25164.34" + "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/FilterCore/FilterRules/ComparableValueFilterRule.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/ComparableValueFilterRule.cs index e7ef648e3fe..8362a035156 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/ComparableValueFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/ComparableValueFilterRule.cs @@ -13,9 +13,25 @@ namespace Microsoft.Management.UI.Internal /// The generic parameter. /// </typeparam> [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] - [Serializable] public abstract class ComparableValueFilterRule<T> : FilterRule where T : IComparable { + /// <summary> + /// Initializes a new instance of the <see cref="ComparableValueFilterRule{T}"/> class. + /// </summary> + protected ComparableValueFilterRule() + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="ComparableValueFilterRule{T}"/> class. + /// </summary> + /// <param name="source">The source to initialize from.</param> + protected ComparableValueFilterRule(ComparableValueFilterRule<T> source) + : base(source) + { + this.DefaultNullValueEvaluation = source.DefaultNullValueEvaluation; + } + #region Properties /// <summary> diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/DoesNotEqualFilterRule.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/DoesNotEqualFilterRule.cs index ae209d0e60f..c5d4f36fe55 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/DoesNotEqualFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/DoesNotEqualFilterRule.cs @@ -13,11 +13,10 @@ namespace Microsoft.Management.UI.Internal /// The generic parameter. /// </typeparam> [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] - [Serializable] public class DoesNotEqualFilterRule<T> : EqualsFilterRule<T> where T : IComparable { /// <summary> - /// Initializes a new instance of the DoesNotEqualFilterRule class. + /// Initializes a new instance of the <see cref="DoesNotEqualFilterRule{T}"/> class. /// </summary> public DoesNotEqualFilterRule() { @@ -25,6 +24,15 @@ public DoesNotEqualFilterRule() this.DefaultNullValueEvaluation = true; } + /// <summary> + /// Initializes a new instance of the <see cref="DoesNotEqualFilterRule{T}"/> class. + /// </summary> + /// <param name="source">The source to initialize from.</param> + public DoesNotEqualFilterRule(DoesNotEqualFilterRule<T> source) + : base(source) + { + } + /// <summary> /// Determines if item is not equal to Value. /// </summary> diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/EqualsFilterRule.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/EqualsFilterRule.cs index 7bafd53e411..34a1ecb722d 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/EqualsFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/EqualsFilterRule.cs @@ -14,17 +14,25 @@ namespace Microsoft.Management.UI.Internal /// The generic parameter. /// </typeparam> [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] - [Serializable] public class EqualsFilterRule<T> : SingleValueComparableValueFilterRule<T> where T : IComparable { /// <summary> - /// Initializes a new instance of the EqualsFilterRule class. + /// Initializes a new instance of the <see cref="EqualsFilterRule{T}"/> class. /// </summary> public EqualsFilterRule() { this.DisplayName = UICultureResources.FilterRule_Equals; } + /// <summary> + /// Initializes a new instance of the <see cref="EqualsFilterRule{T}"/> class. + /// </summary> + /// <param name="source">The source to initialize from.</param> + public EqualsFilterRule(EqualsFilterRule<T> source) + : base(source) + { + } + /// <summary> /// Determines if item is equal to Value. /// </summary> diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/FilterRule.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/FilterRule.cs index 800812cdba5..f18c89addf9 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/FilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/FilterRule.cs @@ -9,8 +9,7 @@ namespace Microsoft.Management.UI.Internal /// The base class for all filtering rules. /// </summary> [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] - [Serializable] - public abstract class FilterRule : IEvaluate + public abstract class FilterRule : IEvaluate, IDeepCloneable { /// <summary> /// Gets a value indicating whether the FilterRule can be @@ -34,12 +33,28 @@ public string DisplayName } /// <summary> - /// Initializes a new instance of the FilterRule class. + /// Initializes a new instance of the <see cref="FilterRule"/> class. /// </summary> protected FilterRule() { } + /// <summary> + /// Initializes a new instance of the <see cref="FilterRule"/> class. + /// </summary> + /// <param name="source">The source to initialize from.</param> + protected FilterRule(FilterRule source) + { + ArgumentNullException.ThrowIfNull(source); + this.DisplayName = source.DisplayName; + } + + /// <inheritdoc cref="IDeepCloneable.DeepClone()" /> + public object DeepClone() + { + return Activator.CreateInstance(this.GetType(), new object[] { this }); + } + /// <summary> /// Gets a value indicating whether the supplied item meets the /// criteria specified by this rule. @@ -53,7 +68,6 @@ protected FilterRule() /// <summary> /// Occurs when the values of this rule changes. /// </summary> - [field: NonSerialized] public event EventHandler EvaluationResultInvalidated; /// <summary> diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/FilterRuleExtensions.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/FilterRuleExtensions.cs index bc8e0b02ca6..4a3f8dc2975 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/FilterRuleExtensions.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/FilterRuleExtensions.cs @@ -2,10 +2,6 @@ // Licensed under the MIT License. using System; -using System.Diagnostics; -using System.IO; -using System.Runtime.Serialization; -using System.Runtime.Serialization.Formatters.Binary; namespace Microsoft.Management.UI.Internal { @@ -28,29 +24,7 @@ public static class FilterRuleExtensions public static FilterRule DeepCopy(this FilterRule rule) { ArgumentNullException.ThrowIfNull(rule); - -#pragma warning disable SYSLIB0050 - Debug.Assert(rule.GetType().IsSerializable, "rule is serializable"); -#pragma warning disable SYSLIB0011 - BinaryFormatter formatter = new BinaryFormatter(null, new StreamingContext(StreamingContextStates.Clone)); -#pragma warning restore SYSLIB0011 - MemoryStream ms = new MemoryStream(); - - FilterRule copy = null; - try - { - formatter.Serialize(ms, rule); - - ms.Position = 0; - copy = (FilterRule)formatter.Deserialize(ms); -#pragma warning restore SYSLIB0050 - } - finally - { - ms.Close(); - } - - return copy; + return (FilterRule)rule.DeepClone(); } } } diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsBetweenFilterRule.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsBetweenFilterRule.cs index cbe4a875dd0..f51093510ec 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsBetweenFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsBetweenFilterRule.cs @@ -16,7 +16,6 @@ namespace Microsoft.Management.UI.Internal /// The generic parameter. /// </typeparam> [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] - [Serializable] public class IsBetweenFilterRule<T> : ComparableValueFilterRule<T> where T : IComparable { #region Properties @@ -56,7 +55,7 @@ public ValidatingValue<T> EndValue #region Ctor /// <summary> - /// Initializes a new instance of the IsBetweenFilterRule class. + /// Initializes a new instance of the <see cref="IsBetweenFilterRule{T}"/> class. /// </summary> public IsBetweenFilterRule() { @@ -69,6 +68,20 @@ public IsBetweenFilterRule() this.EndValue.PropertyChanged += this.Value_PropertyChanged; } + /// <summary> + /// Initializes a new instance of the <see cref="IsBetweenFilterRule{T}"/> class. + /// </summary> + /// <param name="source">The source to initialize from.</param> + public IsBetweenFilterRule(IsBetweenFilterRule<T> source) + : base(source) + { + this.StartValue = (ValidatingValue<T>)source.StartValue.DeepClone(); + this.StartValue.PropertyChanged += this.Value_PropertyChanged; + + this.EndValue = (ValidatingValue<T>)source.EndValue.DeepClone(); + this.EndValue.PropertyChanged += this.Value_PropertyChanged; + } + #endregion Ctor #region Public Methods @@ -108,13 +121,6 @@ private void Value_PropertyChanged(object sender, PropertyChangedEventArgs e) } } - [OnDeserialized] - private void Initialize(StreamingContext context) - { - this.StartValue.PropertyChanged += this.Value_PropertyChanged; - this.EndValue.PropertyChanged += this.Value_PropertyChanged; - } - #endregion Value Change Handlers } } diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsEmptyFilterRule.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsEmptyFilterRule.cs index 5ad2ae1247e..71bb7e23e7c 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsEmptyFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsEmptyFilterRule.cs @@ -10,17 +10,25 @@ namespace Microsoft.Management.UI.Internal /// is empty or not. /// </summary> [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] - [Serializable] public class IsEmptyFilterRule : FilterRule { /// <summary> - /// Initializes a new instance of the IsEmptyFilterRule class. + /// Initializes a new instance of the <see cref="IsEmptyFilterRule"/> class. /// </summary> public IsEmptyFilterRule() { this.DisplayName = UICultureResources.FilterRule_IsEmpty; } + /// <summary> + /// Initializes a new instance of the <see cref="IsEmptyFilterRule"/> class. + /// </summary> + /// <param name="source">The source to initialize from.</param> + public IsEmptyFilterRule(IsEmptyFilterRule source) + : base(source) + { + } + /// <summary> /// Gets a values indicating whether the supplied item is empty. /// </summary> diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsGreaterThanFilterRule.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsGreaterThanFilterRule.cs index d098d2a9383..6c7d16f312a 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsGreaterThanFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsGreaterThanFilterRule.cs @@ -14,17 +14,25 @@ namespace Microsoft.Management.UI.Internal /// The generic parameter. /// </typeparam> [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] - [Serializable] public class IsGreaterThanFilterRule<T> : SingleValueComparableValueFilterRule<T> where T : IComparable { /// <summary> - /// Initializes a new instance of the IsGreaterThanFilterRule class. + /// Initializes a new instance of the <see cref="IsGreaterThanFilterRule{T}"/> class. /// </summary> public IsGreaterThanFilterRule() { this.DisplayName = UICultureResources.FilterRule_GreaterThanOrEqual; } + /// <summary> + /// Initializes a new instance of the <see cref="IsGreaterThanFilterRule{T}"/> class. + /// </summary> + /// <param name="source">The source to initialize from.</param> + public IsGreaterThanFilterRule(IsGreaterThanFilterRule<T> source) + : base(source) + { + } + /// <summary> /// Determines if item is greater than Value. /// </summary> diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsLessThanFilterRule.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsLessThanFilterRule.cs index 8539d6edf0e..e1dc3268cc5 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsLessThanFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsLessThanFilterRule.cs @@ -14,17 +14,25 @@ namespace Microsoft.Management.UI.Internal /// The generic parameter. /// </typeparam> [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] - [Serializable] public class IsLessThanFilterRule<T> : SingleValueComparableValueFilterRule<T> where T : IComparable { /// <summary> - /// Initializes a new instance of the IsLessThanFilterRule class. + /// Initializes a new instance of the <see cref="IsLessThanFilterRule{T}"/> class. /// </summary> public IsLessThanFilterRule() { this.DisplayName = UICultureResources.FilterRule_LessThanOrEqual; } + /// <summary> + /// Initializes a new instance of the <see cref="IsLessThanFilterRule{T}"/> class. + /// </summary> + /// <param name="source">The source to initialize from.</param> + public IsLessThanFilterRule(IsLessThanFilterRule<T> source) + : base(source) + { + } + /// <summary> /// Determines if item is less than Value. /// </summary> diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsNotEmptyFilterRule.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsNotEmptyFilterRule.cs index 68e501d1f68..711caee9874 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsNotEmptyFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsNotEmptyFilterRule.cs @@ -10,17 +10,25 @@ namespace Microsoft.Management.UI.Internal /// is empty or not. /// </summary> [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] - [Serializable] public class IsNotEmptyFilterRule : IsEmptyFilterRule { /// <summary> - /// Initializes a new instance of the IsNotEmptyFilterRule class. + /// Initializes a new instance of the <see cref="IsNotEmptyFilterRule"/> class. /// </summary> public IsNotEmptyFilterRule() { this.DisplayName = UICultureResources.FilterRule_IsNotEmpty; } + /// <summary> + /// Initializes a new instance of the <see cref="IsNotEmptyFilterRule"/> class. + /// </summary> + /// <param name="source">The source to initialize from.</param> + public IsNotEmptyFilterRule(IsNotEmptyFilterRule source) + : base(source) + { + } + /// <summary> /// Gets a values indicating whether the supplied item is not empty. /// </summary> diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsNotEmptyValidationRule.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsNotEmptyValidationRule.cs index 31722bfe1f7..cb6eacaaff3 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsNotEmptyValidationRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/IsNotEmptyValidationRule.cs @@ -9,7 +9,6 @@ namespace Microsoft.Management.UI.Internal /// The IsNotEmptyValidationRule checks a value to see if a value is not empty. /// </summary> [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] - [Serializable] public class IsNotEmptyValidationRule : DataErrorInfoValidationRule { #region Properties @@ -51,6 +50,14 @@ public override DataErrorInfoValidationResult Validate(object value, System.Glob } } + /// <inheritdoc cref="IDeepCloneable.DeepClone()" /> + public override object DeepClone() + { + // Instance is stateless. + // return this; + return new IsNotEmptyValidationRule(); + } + #endregion Public Methods internal static bool IsStringNotEmpty(string value) diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/PropertiesTextContainsFilterRule.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/PropertiesTextContainsFilterRule.cs index 2a1cc576b39..8c32530be8c 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/PropertiesTextContainsFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/PropertiesTextContainsFilterRule.cs @@ -12,7 +12,6 @@ namespace Microsoft.Management.UI.Internal /// Represents a filter rule that searches for text within properties on an object. /// </summary> [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] - [Serializable] public class PropertiesTextContainsFilterRule : TextFilterRule { private static readonly string TextContainsCharactersRegexPattern = "{0}"; @@ -29,6 +28,17 @@ public PropertiesTextContainsFilterRule() this.EvaluationResultInvalidated += this.PropertiesTextContainsFilterRule_EvaluationResultInvalidated; } + /// <summary> + /// Initializes a new instance of the <see cref="PropertiesTextContainsFilterRule"/> class. + /// </summary> + /// <param name="source">The source to initialize from.</param> + public PropertiesTextContainsFilterRule(PropertiesTextContainsFilterRule source) + : base(source) + { + this.PropertyNames = new List<string>(source.PropertyNames); + this.EvaluationResultInvalidated += this.PropertiesTextContainsFilterRule_EvaluationResultInvalidated; + } + /// <summary> /// Gets a collection of the names of properties to search in. /// </summary> @@ -120,11 +130,5 @@ private void PropertiesTextContainsFilterRule_EvaluationResultInvalidated(object { this.OnEvaluationResultInvalidated(); } - - [OnDeserialized] - private void Initialize(StreamingContext context) - { - this.EvaluationResultInvalidated += this.PropertiesTextContainsFilterRule_EvaluationResultInvalidated; - } } } diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/PropertyValueSelectorFilterRule.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/PropertyValueSelectorFilterRule.cs index 158ab4e0229..09c732970b0 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/PropertyValueSelectorFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/PropertyValueSelectorFilterRule.cs @@ -16,7 +16,6 @@ namespace Microsoft.Management.UI.Internal /// The generic parameter. /// </typeparam> [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] - [Serializable] public class PropertyValueSelectorFilterRule<T> : SelectorFilterRule where T : IComparable { #region Properties @@ -82,6 +81,17 @@ public PropertyValueSelectorFilterRule(string propertyName, string propertyDispl this.AvailableRules.DisplayNameConverter = new FilterRuleToDisplayNameConverter(); } + /// <summary> + /// Initializes a new instance of the <see cref="PropertyValueSelectorFilterRule{T}"/> class. + /// </summary> + /// <param name="source">The source to initialize from.</param> + public PropertyValueSelectorFilterRule(PropertyValueSelectorFilterRule<T> source) + : base(source) + { + this.PropertyName = source.PropertyName; + this.AvailableRules.DisplayNameConverter = new FilterRuleToDisplayNameConverter(); + } + #endregion Ctor #region Public Methods diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/SelectorFilterRule.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/SelectorFilterRule.cs index da4a62b6f66..d1627ee2281 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/SelectorFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/SelectorFilterRule.cs @@ -10,7 +10,6 @@ namespace Microsoft.Management.UI.Internal /// The SelectorFilterRule represents a rule composed of other rules. /// </summary> [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] - [Serializable] public class SelectorFilterRule : FilterRule { #region Properties @@ -40,7 +39,7 @@ public ValidatingSelectorValue<FilterRule> AvailableRules #region Ctor /// <summary> - /// Creates a new SelectorFilterRule instance. + /// Initializes a new instance of the <see cref="SelectorFilterRule"/> class. /// </summary> public SelectorFilterRule() { @@ -48,6 +47,18 @@ public SelectorFilterRule() this.AvailableRules.SelectedValueChanged += this.AvailableRules_SelectedValueChanged; } + /// <summary> + /// Initializes a new instance of the <see cref="SelectorFilterRule"/> class. + /// </summary> + /// <param name="source">The source to initialize from.</param> + public SelectorFilterRule(SelectorFilterRule source) + : base(source) + { + this.AvailableRules = (ValidatingSelectorValue<FilterRule>)source.AvailableRules.DeepClone(); + this.AvailableRules.SelectedValueChanged += this.AvailableRules_SelectedValueChanged; + this.AvailableRules.SelectedValue.EvaluationResultInvalidated += this.SelectedValue_EvaluationResultInvalidated; + } + #endregion Ctor #region Public Methods @@ -86,8 +97,8 @@ protected void OnSelectedValueChanged(FilterRule oldValue, FilterRule newValue) FilterRuleCustomizationFactory.FactoryInstance.TransferValues(oldValue, newValue); FilterRuleCustomizationFactory.FactoryInstance.ClearValues(oldValue); - newValue.EvaluationResultInvalidated += this.SelectedValue_EvaluationResultInvalidated; oldValue.EvaluationResultInvalidated -= this.SelectedValue_EvaluationResultInvalidated; + newValue.EvaluationResultInvalidated += this.SelectedValue_EvaluationResultInvalidated; this.NotifyEvaluationResultInvalidated(); } @@ -101,13 +112,6 @@ private void SelectedValue_EvaluationResultInvalidated(object sender, EventArgs #region Private Methods - [OnDeserialized] - private void Initialize(StreamingContext context) - { - this.AvailableRules.SelectedValueChanged += this.AvailableRules_SelectedValueChanged; - this.AvailableRules.SelectedValue.EvaluationResultInvalidated += this.SelectedValue_EvaluationResultInvalidated; - } - private void AvailableRules_SelectedValueChanged(object sender, PropertyChangedEventArgs<FilterRule> e) { this.OnSelectedValueChanged(e.OldValue, e.NewValue); diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/SingleValueComparableValueFilterRule.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/SingleValueComparableValueFilterRule.cs index 9486a126820..b26531943fc 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/SingleValueComparableValueFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/SingleValueComparableValueFilterRule.cs @@ -13,7 +13,6 @@ namespace Microsoft.Management.UI.Internal /// </summary> /// <typeparam name="T">The generic parameter.</typeparam> [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] - [Serializable] public abstract class SingleValueComparableValueFilterRule<T> : ComparableValueFilterRule<T> where T : IComparable { #region Properties @@ -44,7 +43,7 @@ public override bool IsValid #region Ctor /// <summary> - /// Initializes a new instance of the SingleValueComparableValueFilterRule class. + /// Initializes a new instance of the <see cref="SingleValueComparableValueFilterRule{T}"/> class. /// </summary> protected SingleValueComparableValueFilterRule() { @@ -52,6 +51,17 @@ protected SingleValueComparableValueFilterRule() this.Value.PropertyChanged += this.Value_PropertyChanged; } + /// <summary> + /// Initializes a new instance of the <see cref="SingleValueComparableValueFilterRule{T}"/> class. + /// </summary> + /// <param name="source">The source to initialize from.</param> + protected SingleValueComparableValueFilterRule(SingleValueComparableValueFilterRule<T> source) + : base(source) + { + this.Value = (ValidatingValue<T>)source.Value.DeepClone(); + this.Value.PropertyChanged += this.Value_PropertyChanged; + } + #endregion Ctor private void Value_PropertyChanged(object sender, PropertyChangedEventArgs e) @@ -61,11 +71,5 @@ private void Value_PropertyChanged(object sender, PropertyChangedEventArgs e) this.NotifyEvaluationResultInvalidated(); } } - - [OnDeserialized] - private void Initialize(StreamingContext context) - { - this.Value.PropertyChanged += this.Value_PropertyChanged; - } } } diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextContainsFilterRule.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextContainsFilterRule.cs index fe581ca2031..beb4a29d23f 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextContainsFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextContainsFilterRule.cs @@ -11,20 +11,28 @@ namespace Microsoft.Management.UI.Internal /// check if it is contains the rule's value within it. /// </summary> [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] - [Serializable] public class TextContainsFilterRule : TextFilterRule { private static readonly string TextContainsCharactersRegexPattern = "{0}"; private static readonly string TextContainsWordsRegexPattern = WordBoundaryRegexPattern + TextContainsCharactersRegexPattern + WordBoundaryRegexPattern; /// <summary> - /// Initializes a new instance of the TextContainsFilterRule class. + /// Initializes a new instance of the <see cref="TextContainsFilterRule"/> class. /// </summary> public TextContainsFilterRule() { this.DisplayName = UICultureResources.FilterRule_Contains; } + /// <summary> + /// Initializes a new instance of the <see cref="TextContainsFilterRule"/> class. + /// </summary> + /// <param name="source">The source to initialize from.</param> + public TextContainsFilterRule(TextContainsFilterRule source) + : base(source) + { + } + /// <summary> /// Determines if Value is contained within data. /// </summary> diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextDoesNotContainFilterRule.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextDoesNotContainFilterRule.cs index 29bec9b4bbf..2cdbf1efcef 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextDoesNotContainFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextDoesNotContainFilterRule.cs @@ -10,11 +10,10 @@ namespace Microsoft.Management.UI.Internal /// check if it is does not contain the rule's value within it. /// </summary> [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] - [Serializable] public class TextDoesNotContainFilterRule : TextContainsFilterRule { /// <summary> - /// Initializes a new instance of the TextDoesNotContainFilterRule class. + /// Initializes a new instance of the <see cref="TextDoesNotContainFilterRule"/> class. /// </summary> public TextDoesNotContainFilterRule() { @@ -22,6 +21,15 @@ public TextDoesNotContainFilterRule() this.DefaultNullValueEvaluation = true; } + /// <summary> + /// Initializes a new instance of the <see cref="TextDoesNotContainFilterRule"/> class. + /// </summary> + /// <param name="source">The source to initialize from.</param> + public TextDoesNotContainFilterRule(TextDoesNotContainFilterRule source) + : base(source) + { + } + /// <summary> /// Determines if Value is not contained within data. /// </summary> diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextDoesNotEqualFilterRule.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextDoesNotEqualFilterRule.cs index 4e72fe16e67..e74b371a7a6 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextDoesNotEqualFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextDoesNotEqualFilterRule.cs @@ -10,11 +10,10 @@ namespace Microsoft.Management.UI.Internal /// check if it is not equal to the rule's value. /// </summary> [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] - [Serializable] public class TextDoesNotEqualFilterRule : TextEqualsFilterRule { /// <summary> - /// Initializes a new instance of the TextDoesNotEqualFilterRule class. + /// Initializes a new instance of the <see cref="TextDoesNotEqualFilterRule"/> class. /// </summary> public TextDoesNotEqualFilterRule() { @@ -22,6 +21,15 @@ public TextDoesNotEqualFilterRule() this.DefaultNullValueEvaluation = true; } + /// <summary> + /// Initializes a new instance of the <see cref="TextDoesNotEqualFilterRule"/> class. + /// </summary> + /// <param name="source">The source to initialize from.</param> + public TextDoesNotEqualFilterRule(TextDoesNotEqualFilterRule source) + : base(source) + { + } + /// <summary> /// Determines if data is not equal to Value. /// </summary> diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextEndsWithFilterRule.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextEndsWithFilterRule.cs index baca67801bf..d7f7e05c4b8 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextEndsWithFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextEndsWithFilterRule.cs @@ -11,20 +11,28 @@ namespace Microsoft.Management.UI.Internal /// check if it ends with the rule's value. /// </summary> [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] - [Serializable] public class TextEndsWithFilterRule : TextFilterRule { private static readonly string TextEndsWithCharactersRegexPattern = "{0}$"; private static readonly string TextEndsWithWordsRegexPattern = WordBoundaryRegexPattern + TextEndsWithCharactersRegexPattern; /// <summary> - /// Initializes a new instance of the TextEndsWithFilterRule class. + /// Initializes a new instance of the <see cref="TextEndsWithFilterRule"/> class. /// </summary> public TextEndsWithFilterRule() { this.DisplayName = UICultureResources.FilterRule_TextEndsWith; } + /// <summary> + /// Initializes a new instance of the <see cref="TextEndsWithFilterRule"/> class. + /// </summary> + /// <param name="source">The source to initialize from.</param> + public TextEndsWithFilterRule(TextEndsWithFilterRule source) + : base(source) + { + } + /// <summary> /// Determines if data ends with Value. /// </summary> diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextEqualsFilterRule.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextEqualsFilterRule.cs index e49dd9b4a0d..a357575c6ab 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextEqualsFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextEqualsFilterRule.cs @@ -11,19 +11,27 @@ namespace Microsoft.Management.UI.Internal /// check if it is equal to the rule's value. /// </summary> [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] - [Serializable] public class TextEqualsFilterRule : TextFilterRule { private static readonly string TextEqualsCharactersRegexPattern = "^{0}$"; /// <summary> - /// Initializes a new instance of the TextEqualsFilterRule class. + /// Initializes a new instance of the <see cref="TextEqualsFilterRule"/> class. /// </summary> public TextEqualsFilterRule() { this.DisplayName = UICultureResources.FilterRule_Equals; } + /// <summary> + /// Initializes a new instance of the <see cref="TextEqualsFilterRule"/> class. + /// </summary> + /// <param name="source">The source to initialize from.</param> + public TextEqualsFilterRule(TextEqualsFilterRule source) + : base(source) + { + } + /// <summary> /// Determines if data is equal to Value. /// </summary> diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextFilterRule.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextFilterRule.cs index 0dc75cf24e4..eacbcb8d256 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextFilterRule.cs @@ -14,7 +14,6 @@ namespace Microsoft.Management.UI.Internal /// evaluating string operations. /// </summary> [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] - [Serializable] public abstract class TextFilterRule : SingleValueComparableValueFilterRule<string> { /// <summary> @@ -62,7 +61,7 @@ public bool CultureInvariant } /// <summary> - /// Initializes a new instance of the TextFilterRule class. + /// Initializes a new instance of the <see cref="TextFilterRule"/> class. /// </summary> protected TextFilterRule() { @@ -70,6 +69,17 @@ protected TextFilterRule() this.CultureInvariant = false; } + /// <summary> + /// Initializes a new instance of the <see cref="TextFilterRule"/> class. + /// </summary> + /// <param name="source">The source to initialize from.</param> + protected TextFilterRule(TextFilterRule source) + : base(source) + { + this.IgnoreCase = source.IgnoreCase; + this.CultureInvariant = source.CultureInvariant; + } + /// <summary> /// Gets the current value and determines whether it should be evaluated as an exact match. /// </summary> diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextStartsWithFilterRule.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextStartsWithFilterRule.cs index e97deb0fd46..98eac2b9a41 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextStartsWithFilterRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/FilterRules/TextStartsWithFilterRule.cs @@ -11,20 +11,28 @@ namespace Microsoft.Management.UI.Internal /// check if it starts with the rule's value. /// </summary> [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] - [Serializable] public class TextStartsWithFilterRule : TextFilterRule { private static readonly string TextStartsWithCharactersRegexPattern = "^{0}"; private static readonly string TextStartsWithWordsRegexPattern = TextStartsWithCharactersRegexPattern + WordBoundaryRegexPattern; /// <summary> - /// Initializes a new instance of the TextStartsWithFilterRule class. + /// Initializes a new instance of the <see cref="TextStartsWithFilterRule"/> class. /// </summary> public TextStartsWithFilterRule() { this.DisplayName = UICultureResources.FilterRule_TextStartsWith; } + /// <summary> + /// Initializes a new instance of the <see cref="TextStartsWithFilterRule"/> class. + /// </summary> + /// <param name="source">The source to initialize from.</param> + public TextStartsWithFilterRule(TextStartsWithFilterRule source) + : base(source) + { + } + /// <summary> /// Determines if data starts with Value. /// </summary> diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/IDeepCloneable.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/IDeepCloneable.cs new file mode 100644 index 00000000000..841a2424b51 --- /dev/null +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/IDeepCloneable.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Management.UI.Internal +{ + /// <summary> + /// Defines a generalized method for creating a deep copy of an instance. + /// </summary> + internal interface IDeepCloneable + { + /// <summary> + /// Creates a deep copy of the current instance. + /// </summary> + /// <returns>A new object that is a deep copy of the current instance.</returns> + object DeepClone(); + } +} diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/ValidatingSelectorValue.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/ValidatingSelectorValue.cs index 30b2fe8fac1..0fed0c42e65 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/ValidatingSelectorValue.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/ValidatingSelectorValue.cs @@ -16,9 +16,39 @@ namespace Microsoft.Management.UI.Internal /// The generic parameter. /// </typeparam> [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] - [Serializable] public class ValidatingSelectorValue<T> : ValidatingValueBase { + /// <summary> + /// Initializes a new instance of the <see cref="ValidatingSelectorValue{T}"/> class. + /// </summary> + public ValidatingSelectorValue() + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="ValidatingSelectorValue{T}"/> class. + /// </summary> + /// <param name="source">The source to initialize from.</param> + public ValidatingSelectorValue(ValidatingSelectorValue<T> source) + : base(source) + { + availableValues.EnsureCapacity(source.availableValues.Count); + if (typeof(IDeepCloneable).IsAssignableFrom(typeof(T))) + { + foreach (var value in source.availableValues) + { + availableValues.Add((T)((IDeepCloneable)value).DeepClone()); + } + } + else + { + availableValues.AddRange(source.availableValues); + } + + selectedIndex = source.selectedIndex; + displayNameConverter = source.displayNameConverter; + } + #region Properties #region Consts @@ -143,13 +173,18 @@ public IValueConverter DisplayNameConverter /// <summary> /// Notifies listeners that the selected value has changed. /// </summary> - [field: NonSerialized] public event EventHandler<PropertyChangedEventArgs<T>> SelectedValueChanged; #endregion Events #region Public Methods + /// <inheritdoc cref="IDeepCloneable.DeepClone()" /> + public override object DeepClone() + { + return new ValidatingSelectorValue<T>(this); + } + #region Validate /// <summary> diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/ValidatingValue.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/ValidatingValue.cs index fe21d2fee37..437cb3be50e 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/ValidatingValue.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/ValidatingValue.cs @@ -15,9 +15,25 @@ namespace Microsoft.Management.UI.Internal /// The generic parameter. /// </typeparam> [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] - [Serializable] public class ValidatingValue<T> : ValidatingValueBase { + /// <summary> + /// Initializes a new instance of the <see cref="ValidatingValue{T}"/> class. + /// </summary> + public ValidatingValue() + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="ValidatingValue{T}"/> class. + /// </summary> + /// <param name="source">The source to initialize from.</param> + public ValidatingValue(ValidatingValue<T> source) + : base(source) + { + value = source.Value is IDeepCloneable deepClone ? deepClone.DeepClone() : source.Value; + } + #region Properties #region Value @@ -50,6 +66,12 @@ public object Value #region Public Methods + /// <inheritdoc cref="IDeepCloneable.DeepClone()" /> + public override object DeepClone() + { + return new ValidatingValue<T>(this); + } + /// <summary> /// Gets the raw value cast/transformed into /// type T. diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/ValidatingValueBase.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/ValidatingValueBase.cs index d1c349dc32c..a4ffb1af77c 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/ValidatingValueBase.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/ValidatingValueBase.cs @@ -15,9 +15,29 @@ namespace Microsoft.Management.UI.Internal /// classes to support validation via the IDataErrorInfo interface. /// </summary> [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] - [Serializable] - public abstract class ValidatingValueBase : IDataErrorInfo, INotifyPropertyChanged + public abstract class ValidatingValueBase : IDataErrorInfo, INotifyPropertyChanged, IDeepCloneable { + /// <summary> + /// Initializes a new instance of the <see cref="ValidatingValueBase"/> class. + /// </summary> + protected ValidatingValueBase() + { + } + + /// <summary> + /// Initializes a new instance of the <see cref="ValidatingValueBase"/> class. + /// </summary> + /// <param name="source">The source to initialize from.</param> + protected ValidatingValueBase(ValidatingValueBase source) + { + ArgumentNullException.ThrowIfNull(source); + validationRules.EnsureCapacity(source.validationRules.Count); + foreach (var rule in source.validationRules) + { + validationRules.Add((DataErrorInfoValidationRule)rule.DeepClone()); + } + } + #region Properties #region ValidationRules @@ -26,7 +46,6 @@ public abstract class ValidatingValueBase : IDataErrorInfo, INotifyPropertyChang private ReadOnlyCollection<DataErrorInfoValidationRule> readonlyValidationRules; private bool isValidationRulesCollectionDirty = true; - [field: NonSerialized] private DataErrorInfoValidationResult cachedValidationResult; /// <summary> @@ -120,7 +139,6 @@ public string Error /// <remarks> /// The listeners attached to this event are not serialized. /// </remarks> - [field: NonSerialized] public event PropertyChangedEventHandler PropertyChanged; #endregion PropertyChanged @@ -129,6 +147,9 @@ public string Error #region Public Methods + /// <inheritdoc cref="IDeepCloneable.DeepClone()" /> + public abstract object DeepClone(); + #region AddValidationRule /// <summary> diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/ValidationRules/DataErrorInfoValidationRule.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/ValidationRules/DataErrorInfoValidationRule.cs index 652592aec04..a92916c0717 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/ValidationRules/DataErrorInfoValidationRule.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterCore/ValidationRules/DataErrorInfoValidationRule.cs @@ -9,8 +9,7 @@ namespace Microsoft.Management.UI.Internal /// Provides a way to create a custom rule in order to check the validity of user input. /// </summary> [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] - [Serializable] - public abstract class DataErrorInfoValidationRule + public abstract class DataErrorInfoValidationRule : IDeepCloneable { /// <summary> /// When overridden in a derived class, performs validation checks on a value. @@ -25,5 +24,8 @@ public abstract class DataErrorInfoValidationRule /// A DataErrorInfoValidationResult object. /// </returns> public abstract DataErrorInfoValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo); + + /// <inheritdoc cref="IDeepCloneable.DeepClone()" /> + public abstract object DeepClone(); } } diff --git a/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/FilterRuleToDisplayNameConverter.cs b/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/FilterRuleToDisplayNameConverter.cs index aaca30ff321..972c19080e0 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/FilterRuleToDisplayNameConverter.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/FilterProviders/FilterRuleToDisplayNameConverter.cs @@ -12,7 +12,6 @@ namespace Microsoft.Management.UI.Internal /// a FilterRule value to its DisplayName. /// </summary> [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] - [Serializable] public class FilterRuleToDisplayNameConverter : IValueConverter { /// <summary> 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/ManagementList/ManagementList/ManagementListStateDescriptor.cs b/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/ManagementListStateDescriptor.cs index 668118a5b7f..bbfd3d8603c 100644 --- a/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/ManagementListStateDescriptor.cs +++ b/src/Microsoft.Management.UI.Internal/ManagementList/ManagementList/ManagementListStateDescriptor.cs @@ -16,7 +16,6 @@ namespace Microsoft.Management.UI.Internal /// Allows the state of the ManagementList to be saved and restored. /// </summary> [SuppressMessage("Microsoft.MSInternal", "CA903:InternalNamespaceShouldNotContainPublicTypes")] - [Serializable] public class ManagementListStateDescriptor : StateDescriptor<ManagementList> { #region Fields 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 3b01307f437..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.2.25163.2" /> + <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 387da67b864..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.2.25163.2" /> + <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/NewPropertyCommand.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/NewPropertyCommand.cs index caef4af84dc..fb134ce7a11 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/NewPropertyCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/NewPropertyCommand.cs @@ -189,7 +189,32 @@ protected override void ProcessRecord() /// </summary> public class PropertyTypeArgumentCompleter : IArgumentCompleter { - private static readonly string[] s_RegistryPropertyTypes = new string[] + private static readonly CompletionHelpers.CompletionDisplayInfoMapper RegistryPropertyTypeDisplayInfoMapper = registryPropertyType => registryPropertyType switch + { + "String" => ( + ToolTip: TabCompletionStrings.RegistryStringToolTip, + ListItemText: "String"), + "ExpandString" => ( + ToolTip: TabCompletionStrings.RegistryExpandStringToolTip, + ListItemText: "ExpandString"), + "Binary" => ( + ToolTip: TabCompletionStrings.RegistryBinaryToolTip, + ListItemText: "Binary"), + "DWord" => ( + ToolTip: TabCompletionStrings.RegistryDWordToolTip, + ListItemText: "DWord"), + "MultiString" => ( + ToolTip: TabCompletionStrings.RegistryMultiStringToolTip, + ListItemText: "MultiString"), + "QWord" => ( + ToolTip: TabCompletionStrings.RegistryQWordToolTip, + ListItemText: "QWord"), + _ => ( + ToolTip: TabCompletionStrings.RegistryUnknownToolTip, + ListItemText: "Unknown"), + }; + + private static readonly IReadOnlyList<string> s_RegistryPropertyTypes = new List<string>(capacity: 7) { "String", "ExpandString", @@ -200,17 +225,6 @@ public class PropertyTypeArgumentCompleter : IArgumentCompleter "Unknown" }; - private static string GetRegistryPropertyTypeToolTip(string propertyTypeName) => propertyTypeName switch - { - "String" => TabCompletionStrings.RegistryStringToolTip, - "ExpandString" => TabCompletionStrings.RegistryExpandStringToolTip, - "Binary" => TabCompletionStrings.RegistryBinaryToolTip, - "DWord" => TabCompletionStrings.RegistryDWordToolTip, - "MultiString" => TabCompletionStrings.RegistryMultiStringToolTip, - "QWord" => TabCompletionStrings.RegistryQWordToolTip, - _ => TabCompletionStrings.RegistryUnknownToolTip - }; - /// <summary> /// Returns completion results for PropertyType parameter. /// </summary> @@ -230,7 +244,7 @@ public IEnumerable<CompletionResult> CompleteArgument( ? CompletionHelpers.GetMatchingResults( wordToComplete, possibleCompletionValues: s_RegistryPropertyTypes, - toolTipMapping: GetRegistryPropertyTypeToolTip, + displayInfoMapper: RegistryPropertyTypeDisplayInfoMapper, resultType: CompletionResultType.ParameterValue) : []; 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 d2a96b50861..a00f9583d6a 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Service.cs @@ -610,146 +610,197 @@ public string[] Name /// </summary> protected override void ProcessRecord() { - foreach (ServiceController service in MatchingServices()) + nint scManagerHandle = nint.Zero; + if (!DependentServices && !RequiredServices) { - if (!DependentServices.IsPresent && !RequiredServices.IsPresent) - { - WriteObject(AddProperties(service)); + // As Get-Service only works on local services we get this once + // to retrieve extra properties added by PowerShell. + scManagerHandle = NativeMethods.OpenSCManagerW( + lpMachineName: null, + lpDatabaseName: null, + dwDesiredAccess: NativeMethods.SC_MANAGER_CONNECT); + if (scManagerHandle == nint.Zero) + { + Win32Exception exception = new(); + string message = StringUtil.Format(ServiceResources.FailToOpenServiceControlManager, exception.Message); + ServiceCommandException serviceException = new ServiceCommandException(message, exception); + ErrorRecord err = new ErrorRecord( + serviceException, + "FailToOpenServiceControlManager", + ErrorCategory.PermissionDenied, + null); + ThrowTerminatingError(err); } - else + } + + try + { + foreach (ServiceController service in MatchingServices()) { - if (DependentServices.IsPresent) + if (!DependentServices.IsPresent && !RequiredServices.IsPresent) + { + WriteObject(AddProperties(scManagerHandle, service)); + } + else { - foreach (ServiceController dependantserv in service.DependentServices) + if (DependentServices.IsPresent) { - WriteObject(dependantserv); + foreach (ServiceController dependantserv in service.DependentServices) + { + WriteObject(dependantserv); + } } - } - if (RequiredServices.IsPresent) - { - foreach (ServiceController servicedependedon in service.ServicesDependedOn) + if (RequiredServices.IsPresent) { - WriteObject(servicedependedon); + foreach (ServiceController servicedependedon in service.ServicesDependedOn) + { + WriteObject(servicedependedon); + } } } } } + finally + { + if (scManagerHandle != nint.Zero) + { + bool succeeded = NativeMethods.CloseServiceHandle(scManagerHandle); + Diagnostics.Assert(succeeded, "SCManager handle close failed"); + } + } } #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 PSObject AddProperties(ServiceController service) + private PSObject AddProperties(nint scManagerHandle, ServiceController service) { - NakedWin32Handle hScManager = IntPtr.Zero; - NakedWin32Handle hService = IntPtr.Zero; - int lastError = 0; - PSObject serviceAsPSObj = PSObject.AsPSObject(service); + NakedWin32Handle hService = nint.Zero; + + // As these are optional values, a failure due to permissions or + // other problem is ignored and the properties are set to null. + bool? isDelayedAutoStart = null; + string? binPath = null; + string? description = null; + string? startName = null; + ServiceStartupType startupType = ServiceStartupType.InvalidValue; try { - hScManager = NativeMethods.OpenSCManagerW( - lpMachineName: service.MachineName, - lpDatabaseName: null, - dwDesiredAccess: NativeMethods.SC_MANAGER_CONNECT - ); - if (hScManager == IntPtr.Zero) - { - lastError = Marshal.GetLastWin32Error(); - Win32Exception exception = new(lastError); - WriteNonTerminatingError( - service, - exception, - "FailToOpenServiceControlManager", - ServiceResources.FailToOpenServiceControlManager, - ErrorCategory.PermissionDenied); - } - + // We don't use service.ServiceHandle as that requests + // SERVICE_ALL_ACCESS when we only need SERVICE_QUERY_CONFIG. hService = NativeMethods.OpenServiceW( - hScManager, + scManagerHandle, service.ServiceName, NativeMethods.SERVICE_QUERY_CONFIG ); - if (hService == IntPtr.Zero) + if (hService != nint.Zero) { - lastError = Marshal.GetLastWin32Error(); - Win32Exception exception = new(lastError); - WriteNonTerminatingError( - service, - exception, - "CouldNotGetServiceInfo", - ServiceResources.CouldNotGetServiceInfo, - ErrorCategory.PermissionDenied); - } - - NativeMethods.SERVICE_DESCRIPTIONW description = new(); - bool querySuccessful = NativeMethods.QueryServiceConfig2<NativeMethods.SERVICE_DESCRIPTIONW>(hService, NativeMethods.SERVICE_CONFIG_DESCRIPTION, out description); - - NativeMethods.SERVICE_DELAYED_AUTO_START_INFO autostartInfo = new(); - querySuccessful = querySuccessful && NativeMethods.QueryServiceConfig2<NativeMethods.SERVICE_DELAYED_AUTO_START_INFO>(hService, NativeMethods.SERVICE_CONFIG_DELAYED_AUTO_START_INFO, out autostartInfo); + if (NativeMethods.QueryServiceConfig2( + hService, + NativeMethods.SERVICE_CONFIG_DESCRIPTION, + out NativeMethods.SERVICE_DESCRIPTIONW descriptionInfo)) + { + description = descriptionInfo.lpDescription; + } + else + { + WriteServicePropertyError(service.ServiceName, nameof(NativeMethods.SERVICE_DESCRIPTIONW)); + } - NativeMethods.QUERY_SERVICE_CONFIG serviceInfo = new(); - querySuccessful = querySuccessful && NativeMethods.QueryServiceConfig(hService, out serviceInfo); + if (NativeMethods.QueryServiceConfig2( + hService, + NativeMethods.SERVICE_CONFIG_DELAYED_AUTO_START_INFO, + out NativeMethods.SERVICE_DELAYED_AUTO_START_INFO autostartInfo)) + { + isDelayedAutoStart = autostartInfo.fDelayedAutostart; + } + else + { + WriteServicePropertyError(service.ServiceName, nameof(NativeMethods.SERVICE_DELAYED_AUTO_START_INFO)); + } - if (!querySuccessful) + if (NativeMethods.QueryServiceConfig( + hService, + out NativeMethods.QUERY_SERVICE_CONFIG serviceInfo)) + { + binPath = serviceInfo.lpBinaryPathName; + startName = serviceInfo.lpServiceStartName; + if (isDelayedAutoStart.HasValue) + { + startupType = NativeMethods.GetServiceStartupType( + (ServiceStartMode)serviceInfo.dwStartType, + isDelayedAutoStart.Value); + } + } + else + { + WriteServicePropertyError(service.ServiceName, nameof(NativeMethods.QUERY_SERVICE_CONFIG)); + } + } + else { - WriteNonTerminatingError( - service: service, - innerException: null, - errorId: "CouldNotGetServiceInfo", - errorMessage: ServiceResources.CouldNotGetServiceInfo, - category: ErrorCategory.PermissionDenied - ); + // handle when OpenServiceW itself fails: + WriteServicePropertyError(service.ServiceName, nameof(NativeMethods.SERVICE_QUERY_CONFIG)); } - - PSProperty noteProperty = new("UserName", serviceInfo.lpServiceStartName); - serviceAsPSObj.Properties.Add(noteProperty, true); - serviceAsPSObj.TypeNames.Insert(0, "System.Service.ServiceController#UserName"); - - noteProperty = new PSProperty("Description", description.lpDescription); - serviceAsPSObj.Properties.Add(noteProperty, true); - serviceAsPSObj.TypeNames.Insert(0, "System.Service.ServiceController#Description"); - - noteProperty = new PSProperty("DelayedAutoStart", autostartInfo.fDelayedAutostart); - serviceAsPSObj.Properties.Add(noteProperty, true); - serviceAsPSObj.TypeNames.Insert(0, "System.Service.ServiceController#DelayedAutoStart"); - - noteProperty = new PSProperty("BinaryPathName", serviceInfo.lpBinaryPathName); - serviceAsPSObj.Properties.Add(noteProperty, true); - serviceAsPSObj.TypeNames.Insert(0, "System.Service.ServiceController#BinaryPathName"); - - noteProperty = new PSProperty("StartupType", NativeMethods.GetServiceStartupType(service.StartType, autostartInfo.fDelayedAutostart)); - serviceAsPSObj.Properties.Add(noteProperty, true); - serviceAsPSObj.TypeNames.Insert(0, "System.Service.ServiceController#StartupType"); } finally { if (hService != IntPtr.Zero) { bool succeeded = NativeMethods.CloseServiceHandle(hService); - if (!succeeded) - { - Diagnostics.Assert(lastError != 0, "ErrorCode not success"); - } - } - - if (hScManager != IntPtr.Zero) - { - bool succeeded = NativeMethods.CloseServiceHandle(hScManager); - if (!succeeded) - { - Diagnostics.Assert(lastError != 0, "ErrorCode not success"); - } + Diagnostics.Assert(succeeded, "Failed to close service handle"); } } + PSObject serviceAsPSObj = PSObject.AsPSObject(service); + PSNoteProperty noteProperty = new("UserName", startName); + serviceAsPSObj.Properties.Add(noteProperty, true); + serviceAsPSObj.TypeNames.Insert(0, "System.Service.ServiceController#UserName"); + + noteProperty = new PSNoteProperty("Description", description); + serviceAsPSObj.Properties.Add(noteProperty, true); + serviceAsPSObj.TypeNames.Insert(0, "System.Service.ServiceController#Description"); + + noteProperty = new PSNoteProperty("DelayedAutoStart", isDelayedAutoStart); + serviceAsPSObj.Properties.Add(noteProperty, true); + serviceAsPSObj.TypeNames.Insert(0, "System.Service.ServiceController#DelayedAutoStart"); + + noteProperty = new PSNoteProperty("BinaryPathName", binPath); + serviceAsPSObj.Properties.Add(noteProperty, true); + serviceAsPSObj.TypeNames.Insert(0, "System.Service.ServiceController#BinaryPathName"); + + noteProperty = new PSNoteProperty("StartupType", startupType); + serviceAsPSObj.Properties.Add(noteProperty, true); + serviceAsPSObj.TypeNames.Insert(0, "System.Service.ServiceController#StartupType"); + return serviceAsPSObj; } } +#nullable disable #endregion GetServiceCommand #region ServiceOperationBaseCommand 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 3792ce8db99..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. --> @@ -159,9 +159,6 @@ <data name="CouldNotSetService" xml:space="preserve"> <value>Service '{1} ({0})' cannot be configured due to the following error: {2}</value> </data> - <data name="CouldNotGetServiceInfo" xml:space="preserve"> - <value>Service '{1} ({0})' cannot be queried due to the following error: {2}</value> - </data> <data name="CouldNotSetServiceDescription" xml:space="preserve"> <value>Service '{1} ({0})' description cannot be configured due to the following error: {2}</value> </data> @@ -211,9 +208,12 @@ <value>Service '{1} ({0})' resume failed.</value> </data> <data name="FailToOpenServiceControlManager" xml:space="preserve"> - <value>Failed to configure the service '{1} ({0})' due to the following error: {2}. Run PowerShell as admin and run your command again.</value> + <value>Failed to open SCManager due to the following error: {0}. Run PowerShell as admin and run your command again.</value> </data> <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 5926c2ec20f..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.40.0" /> + <PackageReference Include="Markdig.Signed" Version="1.1.2" /> <PackageReference Include="Microsoft.PowerShell.MarkdownRender" Version="7.2.1" /> </ItemGroup> @@ -32,10 +32,9 @@ </ItemGroup> <ItemGroup> - <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.13.0" /> - <PackageReference Include="System.Threading.AccessControl" Version="10.0.0-preview.2.25163.2" /> - <PackageReference Include="System.Drawing.Common" Version="10.0.0-preview.2.25163.9" /> - <PackageReference Include="JsonSchema.Net" Version="7.3.4" /> + <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> </Project> diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddType.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddType.cs index 81ca82cb3c3..7dc0a9c3556 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddType.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddType.cs @@ -684,6 +684,7 @@ private void LoadAssemblies(IEnumerable<string> assemblies) { // CoreCLR doesn't allow re-load TPA assemblies with different API (i.e. we load them by name and now want to load by path). // LoadAssemblyHelper helps us avoid re-loading them, if they already loaded. + // codeql[cs/dll-injection-remote] - This is expected PowerShell behavior and integral to the purpose of the class. It allows users to load any C# dependencies they need for their PowerShell application and add other types they require. Assembly assembly = LoadAssemblyHelper(assemblyName) ?? Assembly.LoadFrom(ResolveAssemblyName(assemblyName, false)); if (PassThru) 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 663ff015556..d01d144caca 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImplicitRemotingCommands.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImplicitRemotingCommands.cs @@ -1705,7 +1705,7 @@ private void HandleHostCallReceived(object sender, RemoteDataEventArgs<RemoteHos internal List<CommandMetadata> GetRemoteCommandMetadata(out Dictionary<string, string> alias2resolvedCommandName) { bool isReleaseCandidateBackcompatibilityMode = - this.Session.Runspace.GetRemoteProtocolVersion() == RemotingConstants.ProtocolVersionWin7RC; + this.Session.Runspace.GetRemoteProtocolVersion() == RemotingConstants.ProtocolVersion_2_0; alias2resolvedCommandName = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); if ((this.CommandName == null) || (this.CommandName.Length == 0) || @@ -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/Join-String.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Join-String.cs index 0aebabe94c7..80a04b07f17 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Join-String.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Join-String.cs @@ -41,7 +41,7 @@ public sealed class JoinStringCommand : PSCmdlet /// Gets or sets the delimiter to join the output with. /// </summary> [Parameter(Position = 1)] - [ArgumentCompleter(typeof(JoinItemCompleter))] + [ArgumentCompleter(typeof(SeparatorArgumentCompleter))] [AllowEmptyString] public string Separator { @@ -79,7 +79,7 @@ public string Separator /// Gets or sets a format string that is applied to each input object. /// </summary> [Parameter(ParameterSetName = "Format")] - [ArgumentCompleter(typeof(JoinItemCompleter))] + [ArgumentCompleter(typeof(FormatStringArgumentCompleter))] public string FormatString { get; set; } /// <summary> @@ -160,79 +160,115 @@ protected override void EndProcessing() } } - [SuppressMessage( - "Microsoft.Performance", - "CA1812:AvoidUninstantiatedInternalClasses", - Justification = "Class is instantiated through late-bound reflection")] - internal class JoinItemCompleter : IArgumentCompleter + /// <summary> + /// Provides completion for the Separator parameter of the Join-String cmdlet. + /// </summary> + public sealed class SeparatorArgumentCompleter : IArgumentCompleter { + private const string NewLineText = +#if UNIX + "`n"; +#else + "`r`n"; +#endif + + private static readonly CompletionHelpers.CompletionDisplayInfoMapper SeparatorDisplayInfoMapper = separator => separator switch + { + "," => ( + ToolTip: TabCompletionStrings.SeparatorCommaToolTip, + ListItemText: "Comma"), + ", " => ( + ToolTip: TabCompletionStrings.SeparatorCommaSpaceToolTip, + ListItemText: "Comma-Space"), + ";" => ( + ToolTip: TabCompletionStrings.SeparatorSemiColonToolTip, + ListItemText: "Semi-Colon"), + "; " => ( + ToolTip: TabCompletionStrings.SeparatorSemiColonSpaceToolTip, + ListItemText: "Semi-Colon-Space"), + "-" => ( + ToolTip: TabCompletionStrings.SeparatorDashToolTip, + ListItemText: "Dash"), + " " => ( + ToolTip: TabCompletionStrings.SeparatorSpaceToolTip, + ListItemText: "Space"), + NewLineText => ( + ToolTip: StringUtil.Format(TabCompletionStrings.SeparatorNewlineToolTip, NewLineText), + ListItemText: "Newline"), + _ => ( + ToolTip: separator, + ListItemText: separator), + }; + + private static readonly IReadOnlyList<string> s_separatorValues = new List<string>(capacity: 7) + { + ",", + ", ", + ";", + "; ", + NewLineText, + "-", + " ", + }; + + /// <summary> + /// Returns completion results for Separator parameter. + /// </summary> + /// <param name="commandName">The command name.</param> + /// <param name="parameterName">The parameter name.</param> + /// <param name="wordToComplete">The word to complete.</param> + /// <param name="commandAst">The command AST.</param> + /// <param name="fakeBoundParameters">The fake bound parameters.</param> + /// <returns>List of Completion Results.</returns> public IEnumerable<CompletionResult> CompleteArgument( string commandName, string parameterName, string wordToComplete, CommandAst commandAst, IDictionary fakeBoundParameters) - { - switch (parameterName) - { - case "Separator": return CompleteSeparator(wordToComplete); - case "FormatString": return CompleteFormatString(wordToComplete); - } - - return null; - } - - private static IEnumerable<CompletionResult> CompleteFormatString(string wordToComplete) - { - var res = new List<CompletionResult>(); - void AddMatching(string completionText) - { - if (completionText.StartsWith(wordToComplete, StringComparison.OrdinalIgnoreCase)) - { - res.Add(new CompletionResult(completionText)); - } - } - - AddMatching("'[{0}]'"); - AddMatching("'{0:N2}'"); - AddMatching("\"`r`n `${0}\""); - AddMatching("\"`r`n [string] `${0}\""); - - return res; - } - - private IEnumerable<CompletionResult> CompleteSeparator(string wordToComplete) - { - var res = new List<CompletionResult>(10); - - void AddMatching(string completionText, string listText, string toolTip) - { - if (completionText.StartsWith(wordToComplete, StringComparison.OrdinalIgnoreCase)) - { - res.Add(new CompletionResult(completionText, listText, CompletionResultType.ParameterValue, toolTip)); - } - } - - AddMatching("', '", "Comma-Space", "', ' - Comma-Space"); - AddMatching("';'", "Semi-Colon", "';' - Semi-Colon "); - AddMatching("'; '", "Semi-Colon-Space", "'; ' - Semi-Colon-Space"); - AddMatching($"\"{NewLineText}\"", "Newline", $"{NewLineText} - Newline"); - AddMatching("','", "Comma", "',' - Comma"); - AddMatching("'-'", "Dash", "'-' - Dash"); - AddMatching("' '", "Space", "' ' - Space"); - return res; - } + => CompletionHelpers.GetMatchingResults( + wordToComplete, + possibleCompletionValues: s_separatorValues, + displayInfoMapper: SeparatorDisplayInfoMapper, + resultType: CompletionResultType.ParameterValue); + } - public string NewLineText + /// <summary> + /// Provides completion for the FormatString parameter of the Join-String cmdlet. + /// </summary> + public sealed class FormatStringArgumentCompleter : IArgumentCompleter + { + private static readonly IReadOnlyList<string> s_formatStringValues = new List<string>(capacity: 4) { - get - { + "[{0}]", + "{0:N2}", #if UNIX - return "`n"; + "`n `${0}", + "`n [string] `${0}", #else - return "`r`n"; + "`r`n `${0}", + "`r`n [string] `${0}", #endif - } - } + }; + + /// <summary> + /// Returns completion results for FormatString parameter. + /// </summary> + /// <param name="commandName">The command name.</param> + /// <param name="parameterName">The parameter name.</param> + /// <param name="wordToComplete">The word to complete.</param> + /// <param name="commandAst">The command AST.</param> + /// <param name="fakeBoundParameters">The fake bound parameters.</param> + /// <returns>List of Completion Results.</returns> + public IEnumerable<CompletionResult> CompleteArgument( + string commandName, + string parameterName, + string wordToComplete, + CommandAst commandAst, + IDictionary fakeBoundParameters) + => CompletionHelpers.GetMatchingResults( + wordToComplete, + possibleCompletionValues: s_formatStringValues, + matchStrategy: CompletionHelpers.WildcardPatternEscapeMatch); } } 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/ContentHelper.Common.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/ContentHelper.Common.cs index bb31366f794..9eca5ce187a 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/ContentHelper.Common.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/Common/ContentHelper.Common.cs @@ -9,7 +9,7 @@ using System.Net.Http; using System.Net.Http.Headers; using System.Text; - +using Humanizer; using Microsoft.Win32; namespace Microsoft.PowerShell.Commands @@ -21,8 +21,15 @@ internal static class ContentHelper // ContentType may not exist in response header. Return null if not. internal static string? GetContentType(HttpResponseMessage response) => response.Content.Headers.ContentType?.MediaType; + internal static string? GetContentType(HttpRequestMessage request) => request.Content?.Headers.ContentType?.MediaType; + internal static Encoding GetDefaultEncoding() => Encoding.UTF8; + internal static string GetFriendlyContentLength(long? length) => + length.HasValue + ? $"{length.Value.Bytes().Humanize()} ({length.Value:#,0} bytes)" + : "unknown size"; + internal static StringBuilder GetRawContentHeader(HttpResponseMessage response) { StringBuilder raw = new(); @@ -133,10 +140,13 @@ internal static bool IsXml([NotNullWhen(true)] string? contentType) || contentType.Equals("application/xml-external-parsed-entity", StringComparison.OrdinalIgnoreCase) || contentType.Equals("application/xml-dtd", StringComparison.OrdinalIgnoreCase) || contentType.EndsWith("+xml", StringComparison.OrdinalIgnoreCase); - + return isXml; } + internal static bool IsTextBasedContentType([NotNullWhen(true)] string? contentType) + => IsText(contentType) || IsJson(contentType) || IsXml(contentType); + #endregion Internal Methods } } 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 20a2a980d71..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 @@ -99,18 +99,22 @@ internal override void ProcessResponse(HttpResponseMessage response) string? characterSet = WebResponseHelper.GetCharacterSet(response); string str = StreamHelper.DecodeStream(responseStream, characterSet, out Encoding encoding, perReadTimeout, _cancelToken.Token); - string encodingVerboseName; + string friendlyName = "unknown"; + string encodingWebName = "unknown"; + string encodingPage = encoding.CodePage == -1 ? "unknown" : encoding.CodePage.ToString(); try { - encodingVerboseName = encoding.HeaderName; + // NOTE: These are getter methods that may possibly throw a NotSupportedException exception, + // hence the try/catch + encodingWebName = encoding.WebName; + friendlyName = encoding.EncodingName; } catch { - encodingVerboseName = string.Empty; } - // NOTE: Tests use this verbose output to verify the encoding. - WriteVerbose($"Content encoding: {encodingVerboseName}"); + // NOTE: Tests use this debug output to verify the encoding. + WriteDebug($"WebResponse content encoding: {encodingWebName} ({friendlyName}) CodePage: {encodingPage}"); // Determine the response type RestReturnType returnType = CheckReturnType(response); @@ -140,7 +144,7 @@ internal override void ProcessResponse(HttpResponseMessage response) responseStream.Position = 0; } - + if (ShouldSaveToOutFile) { string outFilePath = WebResponseHelper.GetOutFilePath(response, _qualifiedOutFile); @@ -350,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 886c04919b6..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 @@ -93,6 +93,11 @@ public abstract class WebRequestPSCmdlet : PSCmdlet, IDisposable { #region Fields + /// <summary> + /// Used to prefix the headers in debug and verbose messaging. + /// </summary> + internal const string DebugHeaderPrefix = "--- "; + /// <summary> /// Cancellation token source. /// </summary> @@ -567,29 +572,10 @@ protected override void ProcessRecord() FillRequestStream(request); try { - long requestContentLength = request.Content is null ? 0 : request.Content.Headers.ContentLength.Value; - - string reqVerboseMsg = string.Format( - CultureInfo.CurrentCulture, - WebCmdletStrings.WebMethodInvocationVerboseMsg, - request.Version, - request.Method, - requestContentLength); - - WriteVerbose(reqVerboseMsg); - _maximumRedirection = WebSession.MaximumRedirection; using HttpResponseMessage response = GetResponse(client, request, handleRedirect); - string contentType = ContentHelper.GetContentType(response); - long? contentLength = response.Content.Headers.ContentLength; - string respVerboseMsg = contentLength is null - ? string.Format(CultureInfo.CurrentCulture, WebCmdletStrings.WebResponseNoSizeVerboseMsg, response.Version, contentType) - : string.Format(CultureInfo.CurrentCulture, WebCmdletStrings.WebResponseVerboseMsg, response.Version, contentLength, contentType); - - WriteVerbose(respVerboseMsg); - bool _isSuccess = response.IsSuccessStatusCode; // Check if the Resume range was not satisfiable because the file already completed downloading. @@ -638,6 +624,9 @@ protected override void ProcessRecord() string detailMsg = string.Empty; try { + string contentType = ContentHelper.GetContentType(response); + long? contentLength = response.Content.Headers.ContentLength; + // We can't use ReadAsStringAsync because it doesn't have per read timeouts TimeSpan perReadTimeout = ConvertTimeoutSecondsToTimeSpan(OperationTimeoutSeconds); string characterSet = WebResponseHelper.GetCharacterSet(response); @@ -1296,7 +1285,28 @@ internal virtual HttpResponseMessage GetResponse(HttpClient client, HttpRequestM _cancelToken = new CancellationTokenSource(); try { + if (IsWriteVerboseEnabled()) + { + WriteWebRequestVerboseInfo(currentRequest); + } + + if (IsWriteDebugEnabled()) + { + 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()) + { + WriteWebResponseVerboseInfo(response); + } + + if (IsWriteDebugEnabled()) + { + WriteWebResponseDebugInfo(response); + } } catch (TaskCanceledException ex) { @@ -1361,17 +1371,6 @@ internal virtual HttpResponseMessage GetResponse(HttpClient client, HttpRequestM { FillRequestStream(requestWithoutRange); - long requestContentLength = requestWithoutRange.Content is null ? 0 : requestWithoutRange.Content.Headers.ContentLength.Value; - - string reqVerboseMsg = string.Format( - CultureInfo.CurrentCulture, - WebCmdletStrings.WebMethodInvocationVerboseMsg, - requestWithoutRange.Version, - requestWithoutRange.Method, - requestContentLength); - - WriteVerbose(reqVerboseMsg); - response.Dispose(); response = GetResponse(client, requestWithoutRange, handleRedirect); } @@ -1431,13 +1430,206 @@ internal virtual void UpdateSession(HttpResponseMessage response) { ArgumentNullException.ThrowIfNull(response); } - #endregion Virtual Methods #region Helper Methods - +#nullable enable internal static TimeSpan ConvertTimeoutSecondsToTimeSpan(int timeout) => timeout > 0 ? TimeSpan.FromSeconds(timeout) : Timeout.InfiniteTimeSpan; + private void WriteWebRequestVerboseInfo(HttpRequestMessage request) + { + try + { + // Typical Basic Example: 'WebRequest: v1.1 POST https://httpstat.us/200 with query length 6' + StringBuilder verboseBuilder = new(128); + + // "Redact" the query string from verbose output, the details will be visible in Debug output + string uriWithoutQuery = request.RequestUri?.GetLeftPart(UriPartial.Path) ?? string.Empty; + verboseBuilder.Append($"WebRequest: v{request.Version} {request.Method} {uriWithoutQuery}"); + if (request.RequestUri?.Query is not null && request.RequestUri.Query.Length > 1) + { + verboseBuilder.Append($" with query length {request.RequestUri.Query.Length - 1}"); + } + + string? requestContentType = ContentHelper.GetContentType(request); + if (requestContentType is not null) + { + verboseBuilder.Append($" with {requestContentType} payload"); + } + + long? requestContentLength = request.Content?.Headers?.ContentLength; + if (requestContentLength is not null) + { + verboseBuilder.Append($" with body size {ContentHelper.GetFriendlyContentLength(requestContentLength)}"); + } + if (OutFile is not null) + { + verboseBuilder.Append($" output to {QualifyFilePath(OutFile)}"); + } + + WriteVerbose(verboseBuilder.ToString().Trim()); + } + catch (Exception ex) + { + // Just in case there are any edge cases we missed, we don't break workflows with an exception + WriteVerbose($"Failed to Write WebRequest Verbose Info: {ex} {ex.StackTrace}"); + } + } + + private void WriteWebRequestDebugInfo(HttpRequestMessage request) + { + try + { + // Typical basic example: + // WebRequest Detail + // ---QUERY + // test = 5 + // --- HEADERS + // User - Agent: Mozilla / 5.0, (Linux;Ubuntu 24.04.2 LTS;en - US), PowerShell / 7.6.0 + StringBuilder debugBuilder = new("WebRequest Detail" + Environment.NewLine, 512); + + if (!string.IsNullOrEmpty(request.RequestUri?.Query)) + { + debugBuilder.Append(DebugHeaderPrefix).AppendLine("QUERY"); + string[] queryParams = request.RequestUri.Query.TrimStart('?').Split('&'); + debugBuilder + .AppendJoin(Environment.NewLine, queryParams) + .AppendLine() + .AppendLine(); + } + + debugBuilder.Append(DebugHeaderPrefix).AppendLine("HEADERS"); + + foreach (var headerSet in new HttpHeaders?[] { request.Headers, request.Content?.Headers }) + { + if (headerSet is null) + { + continue; + } + + debugBuilder.AppendLine(headerSet.ToString()); + } + + if (request.Content is not null) + { + debugBuilder + .Append(DebugHeaderPrefix).AppendLine("BODY") + .AppendLine(request.Content switch + { + StringContent stringContent => stringContent + .ReadAsStringAsync(_cancelToken.Token) + .GetAwaiter().GetResult(), + MultipartFormDataContent multipartContent => "=> Multipart Form Content" + + Environment.NewLine + + multipartContent.ReadAsStringAsync(_cancelToken.Token) + .GetAwaiter().GetResult(), + ByteArrayContent byteContent => InFile is not null + ? "[Binary content: " + + ContentHelper.GetFriendlyContentLength(byteContent.Headers.ContentLength) + + "]" + : byteContent.ReadAsStringAsync(_cancelToken.Token).GetAwaiter().GetResult(), + StreamContent streamContent => + "[Stream content: " + ContentHelper.GetFriendlyContentLength(streamContent.Headers.ContentLength) + "]", + _ => "[Unknown content type]", + }) + .AppendLine(); + } + + WriteDebug(debugBuilder.ToString().Trim()); + } + catch (Exception ex) + { + // Just in case there are any edge cases we missed, we don't break workflows with an exception + WriteVerbose($"Failed to Write WebRequest Debug Info: {ex} {ex.StackTrace}"); + } + } + + private void WriteWebResponseVerboseInfo(HttpResponseMessage response) + { + try + { + // Typical basic example: WebResponse: 200 OK with text/plain payload body size 6 B (6 bytes) + StringBuilder verboseBuilder = new(128); + verboseBuilder.Append($"WebResponse: {(int)response.StatusCode} {response.ReasonPhrase ?? response.StatusCode.ToString()}"); + + string? responseContentType = ContentHelper.GetContentType(response); + if (responseContentType is not null) + { + verboseBuilder.Append($" with {responseContentType} payload"); + } + + long? responseContentLength = response.Content?.Headers?.ContentLength; + if (responseContentLength is not null) + { + verboseBuilder.Append($" with body size {ContentHelper.GetFriendlyContentLength(responseContentLength)}"); + } + + WriteVerbose(verboseBuilder.ToString().Trim()); + } + catch (Exception ex) + { + // Just in case there are any edge cases we missed, we don't break workflows with an exception + WriteVerbose($"Failed to Write WebResponse Verbose Info: {ex} {ex.StackTrace}"); + } + } + + private void WriteWebResponseDebugInfo(HttpResponseMessage response) + { + try + { + // Typical basic example + // WebResponse Detail + // --- HEADERS + // Date: Fri, 09 May 2025 18:06:44 GMT + // Server: Kestrel + // Set-Cookie: ARRAffinity=ee0b467f95b53d8dcfe48aeeb4173f93cf819be6e4721f434341647f4695039d;Path=/;HttpOnly;Secure;Domain=httpstat.us, ARRAffinitySameSite=ee0b467f95b53d8dcfe48aeeb4173f93cf819be6e4721f434341647f4695039d;Path=/;HttpOnly;SameSite=None;Secure;Domain=httpstat.us + // Strict-Transport-Security: max-age=2592000 + // Request-Context: appId=cid-v1:3548b0f5-7f75-492f-82bb-b6eb0e864e53 + // Content-Length: 6 + // Content-Type: text/plain + // --- BODY + // 200 OK + StringBuilder debugBuilder = new("WebResponse Detail" + Environment.NewLine, 512); + + debugBuilder.Append(DebugHeaderPrefix).AppendLine("HEADERS"); + + foreach (var headerSet in new HttpHeaders?[] { response.Headers, response.Content?.Headers }) + { + if (headerSet is null) + { + continue; + } + + debugBuilder.AppendLine(headerSet.ToString()); + } + + if (response.Content is not null) + { + debugBuilder.Append(DebugHeaderPrefix).AppendLine("BODY"); + + if (ContentHelper.IsTextBasedContentType(ContentHelper.GetContentType(response))) + { + debugBuilder.AppendLine( + response.Content.ReadAsStringAsync(_cancelToken.Token) + .GetAwaiter().GetResult()); + } + else + { + string friendlyContentLength = ContentHelper.GetFriendlyContentLength( + response.Content?.Headers?.ContentLength); + debugBuilder.AppendLine($"[Binary content: {friendlyContentLength}]"); + } + } + + WriteDebug(debugBuilder.ToString().Trim()); + } + catch (Exception ex) + { + // Just in case there are any edge cases we missed, we don't break workflows with an exception + WriteVerbose($"Failed to Write WebResponse Debug Info: {ex} {ex.StackTrace}"); + } + } + private Uri PrepareUri(Uri uri) { uri = CheckProtocol(uri); @@ -1472,6 +1664,7 @@ private static Uri CheckProtocol(Uri uri) return uri.IsAbsoluteUri ? uri : new Uri("http://" + uri.OriginalString); } +#nullable restore private string QualifyFilePath(string path) => PathUtils.ResolveFilePath(filePath: path, command: this, isLiteralPath: true); @@ -1771,6 +1964,7 @@ private static StringContent GetMultipartStringContent(object fieldName, object ContentDispositionHeaderValue contentDisposition = new("form-data"); contentDisposition.Name = LanguagePrimitives.ConvertTo<string>(fieldName); + // codeql[cs/information-exposure-through-exception] - PowerShell is an on-premise product, meaning local users would already have access to the binaries and stack traces. Therefore, the information would not be exposed in the same way it would be for an ASP .NET service. StringContent result = new(LanguagePrimitives.ConvertTo<string>(fieldValue)); result.Headers.ContentDisposition = contentDisposition; @@ -1801,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.Commands.Utility/resources/WebCmdletStrings.resx b/src/Microsoft.PowerShell.Commands.Utility/resources/WebCmdletStrings.resx index 6e73043747e..cb080d37012 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/resources/WebCmdletStrings.resx +++ b/src/Microsoft.PowerShell.Commands.Utility/resources/WebCmdletStrings.resx @@ -164,7 +164,7 @@ </data> <data name="InsecureRedirection" xml:space="preserve"> <value>Cannot follow an insecure redirection by default. Reissue the command specifying the -AllowInsecureRedirect switch. </value> - </data> + </data> <data name="KeysWithDifferentCasingInJsonString" xml:space="preserve"> <value>Cannot convert the JSON string because it contains keys with different casing. Please use the -AsHashTable switch instead. The key that was attempted to be added to the existing key '{0}' was '{1}'.</value> </data> @@ -234,15 +234,9 @@ <data name="FollowingRelLinkVerboseMsg" xml:space="preserve"> <value>Following rel link {0}</value> </data> - <data name="WebMethodInvocationVerboseMsg" xml:space="preserve"> - <value>Requested HTTP/{0} {1} with {2}-byte payload</value> - </data> <data name="WebMethodResumeFailedVerboseMsg" xml:space="preserve"> <value>The remote server indicated it could not resume downloading. The local file will be overwritten.</value> </data> - <data name="WebResponseVerboseMsg" xml:space="preserve"> - <value>Received HTTP/{0} {1}-byte response of content type {2}</value> - </data> <data name="WebResponseNoSizeVerboseMsg" xml:space="preserve"> <value>Received HTTP/{0} response of content type {1} of unknown size</value> </data> 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 d81a435bd04..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); } @@ -709,9 +709,14 @@ private void WriteLineToConsole() /// </exception> public override void Write(string value) { - WriteImpl(value, newLine: false); + lock (_instanceLock) + { + WriteImpl(value, newLine: false); + } } + // The WriteImpl() method should always be called within a lock on _instanceLock + // to ensure thread safety and prevent issues in multi-threaded scenarios. private void WriteImpl(string value, bool newLine) { if (string.IsNullOrEmpty(value) && !newLine) @@ -845,7 +850,10 @@ private void Write(ConsoleColor foregroundColor, ConsoleColor backgroundColor, s /// </exception> public override void WriteLine(string value) { - this.WriteImpl(value, newLine: true); + lock (_instanceLock) + { + this.WriteImpl(value, newLine: true); + } } /// <summary> @@ -862,7 +870,10 @@ public override void WriteLine(string value) /// </exception> public override void WriteLine() { - this.WriteImpl(Environment.NewLine, newLine: false); + lock (_instanceLock) + { + this.WriteImpl(Environment.NewLine, newLine: false); + } } #region Word Wrapping @@ -1385,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 f5556d7e8e1..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.2.25163.2" /> + <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 4ac82f133eb..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.2.25163.2" /> - <PackageReference Include="Microsoft.Extensions.ObjectPool" Version="10.0.0-preview.2.25164.1" /> + <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.2.25163.2" /> - <PackageReference Include="System.Net.Http.WinHttpHandler" Version="10.0.0-preview.2.25163.2" /> - <!-- 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.2.25163.3" /> + <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 66a9653e5d2..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.2.25163.2" /> + <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 00a5188ca5c..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.13.0" PrivateAssets="all" /> - <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.12.0-beta1.25081.1" 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 59fc5e09445..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.2.25163.2" /> - <PackageReference Include="System.Configuration.ConfigurationManager" Version="10.0.0-preview.2.25163.2" /> - <!-- Removing due to NU1510 --> - <!-- PackageReference Include="System.Diagnostics.DiagnosticSource" Version="9.0.2" /--> - <PackageReference Include="System.DirectoryServices" Version="10.0.0-preview.2.25163.2" /> - <!--PackageReference Include="System.IO.FileSystem.AccessControl" Version="6.0.0-preview.5.21301.5" /--> - <PackageReference Include="System.Management" Version="10.0.0-preview.2.25163.2" /> - <!-- 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.2.25163.2" /> - <PackageReference Include="System.Security.Permissions" Version="10.0.0-preview.2.25163.2" /> - <!-- 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 e9a28c2a165..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) @@ -499,11 +498,16 @@ internal static List<CompletionResult> CompleteModuleName(CompletionContext cont nestedModulesToFilterOut = new(currentModule.NestedModules); } + var completedModules = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (PSObject item in psObjects) { var moduleInfo = (PSModuleInfo)item.BaseObject; var completionText = moduleInfo.Name; - var listItemText = completionText; + if (!completedModules.Add(completionText)) + { + continue; + } + if (shortNameSearch && completionText.Contains('.') && !shortNamePattern.IsMatch(completionText.Substring(completionText.LastIndexOf('.') + 1)) @@ -526,7 +530,7 @@ internal static List<CompletionResult> CompleteModuleName(CompletionContext cont completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); - result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, toolTip)); + result.Add(new CompletionResult(completionText, listItemText: moduleInfo.Name, CompletionResultType.ParameterValue, toolTip)); } } @@ -598,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; @@ -665,6 +669,11 @@ private static List<CompletionResult> GetParameterCompletionResults(string param { Diagnostics.Assert(bindingInfo.InfoType.Equals(PseudoBindingInfoType.PseudoBindingSucceed), "The pseudo binding should succeed"); List<CompletionResult> result = new List<CompletionResult>(); + Assembly commandAssembly = null; + if (bindingInfo.CommandInfo is CmdletInfo cmdletInfo) + { + commandAssembly = cmdletInfo.CommandMetadata.CommandType.Assembly; + } if (parameterName == string.Empty) { @@ -672,7 +681,8 @@ private static List<CompletionResult> GetParameterCompletionResults(string param parameterName, bindingInfo.ValidParameterSetsFlags, bindingInfo.UnboundParameters, - withColon); + withColon, + commandAssembly); return result; } @@ -694,7 +704,8 @@ private static List<CompletionResult> GetParameterCompletionResults(string param parameterName, bindingInfo.ValidParameterSetsFlags, bindingInfo.UnboundParameters, - withColon); + withColon, + commandAssembly); } return result; @@ -709,7 +720,8 @@ private static List<CompletionResult> GetParameterCompletionResults(string param parameterName, bindingInfo.ValidParameterSetsFlags, bindingInfo.BoundParameters.Values, - withColon); + withColon, + commandAssembly); } return result; @@ -773,7 +785,8 @@ private static List<CompletionResult> GetParameterCompletionResults(string param parameterName, bindingInfo.ValidParameterSetsFlags, bindingInfo.UnboundParameters, - withColon); + withColon, + commandAssembly); return result; } @@ -781,11 +794,25 @@ private static List<CompletionResult> GetParameterCompletionResults(string param WildcardPattern pattern = WildcardPattern.Get(parameterName + "*", WildcardOptions.IgnoreCase); string parameterType = "[" + ToStringCodeMethods.Type(param.Parameter.Type, dropNamespaces: true) + "] "; + + string helpMessage = string.Empty; + if (param.Parameter.CompiledAttributes is not null) + { + foreach (Attribute attr in param.Parameter.CompiledAttributes) + { + if (attr is ParameterAttribute pattr && TryGetParameterHelpMessage(pattr, commandAssembly, out string attrHelpMessage)) + { + helpMessage = $" - {attrHelpMessage}"; + break; + } + } + } + string colonSuffix = withColon ? ":" : string.Empty; if (pattern.IsMatch(matchedParameterName)) { - string completionText = "-" + matchedParameterName + colonSuffix; - string tooltip = parameterType + matchedParameterName; + string completionText = $"-{matchedParameterName}{colonSuffix}"; + string tooltip = $"{parameterType}{matchedParameterName}{helpMessage}"; result.Add(new CompletionResult(completionText, matchedParameterName, CompletionResultType.ParameterName, tooltip)); } else @@ -799,7 +826,7 @@ private static List<CompletionResult> GetParameterCompletionResults(string param $"-{alias}{colonSuffix}", alias, CompletionResultType.ParameterName, - parameterType + alias)); + $"{parameterType}{alias}{helpMessage}")); } } } @@ -807,6 +834,44 @@ private static List<CompletionResult> GetParameterCompletionResults(string param return result; } +#nullable enable + /// <summary> + /// Try and get the help message text for the parameter attribute. + /// </summary> + /// <param name="attr">The attribute to check for the help message.</param> + /// <param name="assembly">The assembly to lookup resources messages, this should be the assembly the cmdlet is defined in.</param> + /// <param name="message">The help message if it was found otherwise null.</param> + /// <returns>True if the help message was set or false if not.></returns> + private static bool TryGetParameterHelpMessage( + ParameterAttribute attr, + Assembly? assembly, + [NotNullWhen(true)] out string? message) + { + message = null; + + if (attr.HelpMessage is not null) + { + message = attr.HelpMessage; + return true; + } + + if (assembly is null || attr.HelpMessageBaseName is null || attr.HelpMessageResourceId is null) + { + return false; + } + + try + { + message = ResourceManagerCache.GetResourceString(assembly, attr.HelpMessageBaseName, attr.HelpMessageResourceId); + return message is not null; + } + catch (Exception) + { + return false; + } + } +#nullable disable + /// <summary> /// Get the parameter completion results by using the given valid parameter sets and available parameters. /// </summary> @@ -814,12 +879,14 @@ private static List<CompletionResult> GetParameterCompletionResults(string param /// <param name="validParameterSetFlags"></param> /// <param name="parameters"></param> /// <param name="withColon"></param> + /// <param name="commandAssembly">Optional assembly used to lookup parameter help messages.</param> /// <returns></returns> private static List<CompletionResult> GetParameterCompletionResults( string parameterName, uint validParameterSetFlags, IEnumerable<MergedCompiledCommandParameter> parameters, - bool withColon) + bool withColon, + Assembly commandAssembly = null) { var result = new List<CompletionResult>(); var commonParamResult = new List<CompletionResult>(); @@ -835,6 +902,7 @@ private static List<CompletionResult> GetParameterCompletionResults( string name = param.Parameter.Name; string type = "[" + ToStringCodeMethods.Type(param.Parameter.Type, dropNamespaces: true) + "] "; + string helpMessage = null; bool isCommonParameter = Cmdlet.CommonParameters.Contains(name, StringComparer.OrdinalIgnoreCase); List<CompletionResult> listInUse = isCommonParameter ? commonParamResult : result; @@ -850,20 +918,27 @@ private static List<CompletionResult> GetParameterCompletionResults( { foreach (var attr in compiledAttributes) { - var pattr = attr as ParameterAttribute; - if (pattr != null && pattr.DontShow) + if (attr is ParameterAttribute pattr) { - showToUser = false; - addCommonParameters = false; - break; + if (pattr.DontShow) + { + showToUser = false; + addCommonParameters = false; + break; + } + + if (helpMessage is null && TryGetParameterHelpMessage(pattr, commandAssembly, out string attrHelpMessage)) + { + helpMessage = $" - {attrHelpMessage}"; + } } } } if (showToUser) { - string completionText = "-" + name + colonSuffix; - string tooltip = type + name; + string completionText = $"-{name}{colonSuffix}"; + string tooltip = $"{type}{name}{helpMessage}"; listInUse.Add(new CompletionResult(completionText, name, CompletionResultType.ParameterName, tooltip)); } @@ -2429,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]); } @@ -2562,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) { @@ -2575,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; @@ -2593,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, @@ -2620,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 @@ -2641,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); } @@ -3049,7 +3158,7 @@ private static void NativeCompletionCimNamespace( continue; } - if (!(namespaceNameProperty.Value is string childNamespace)) + if (namespaceNameProperty.Value is not string childNamespace) { continue; } @@ -4499,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) { @@ -4691,7 +4800,7 @@ private static List<CompletionResult> GetFileSystemProviderResults( var resultType = isContainer ? CompletionResultType.ProviderContainer : CompletionResultType.ProviderItem; - + bool leafQuotesNeeded; var completionText = NewPathCompletionText( basePath, @@ -4915,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('~'); @@ -5038,14 +5147,24 @@ private static void EscapeCharIfNeeded( break; default: - // Handle all the different quote types - if (useSingleQuoteEscapeRules && path[index].IsSingleQuote()) + if (useSingleQuoteEscapeRules) { - _ = sb.Append('\''); - quotesAreNeeded = true; + // Bareword or singlequoted input string. + if (path[index].IsSingleQuote()) + { + // SingleQuotes are escaped with more single quotes. quotesAreNeeded is set so bareword strings can quoted. + _ = sb.Append('\''); + quotesAreNeeded = true; + } + else if (!quotesAreNeeded && stringType == StringConstantType.BareWord && path[index].IsDoubleQuote()) + { + // Bareword string with double quote inside. Make sure to quote it so we don't need to escape it. + quotesAreNeeded = true; + } } - else if (!useSingleQuoteEscapeRules && path[index].IsDoubleQuote()) + else if (path[index].IsDoubleQuote()) { + // Double quoted or bareword with variables input string. Need to escape double quotes. _ = sb.Append('`'); quotesAreNeeded = true; } @@ -5113,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 } @@ -5331,11 +5460,11 @@ internal static List<CompletionResult> CompleteVariable(CompletionContext contex ToStringCodeMethods.Type(value.GetType(), dropNamespaces: true), name); } - } - if (!string.IsNullOrEmpty(variable.Description)) - { - tooltip += $" - {variable.Description}"; + if (!string.IsNullOrEmpty(variable.Description)) + { + tooltip += $" - {variable.Description}"; + } } var completedName = !tokenAtCursorUsedBraces && !ContainsCharactersRequiringQuotes(name) @@ -5573,7 +5702,13 @@ public override AstVisitAction VisitAssignmentStatement(AssignmentStatementAst a : AstVisitAction.StopVisit; } - if (assignmentStatementAst.Left is AttributedExpressionAst attributedExpression) + ProcessAssignmentLeftSide(assignmentStatementAst.Left, assignmentStatementAst.Right); + return AstVisitAction.Continue; + } + + private void ProcessAssignmentLeftSide(ExpressionAst left, StatementAst right) + { + if (left is AttributedExpressionAst attributedExpression) { var firstConvertExpression = attributedExpression as ConvertExpressionAst; ExpressionAst child = attributedExpression.Child; @@ -5593,7 +5728,7 @@ public override AstVisitAction VisitAssignmentStatement(AssignmentStatementAst a { if (variableExpression == CompletionVariableAst || s_specialVariablesCache.Value.Contains(variableExpression.VariablePath.UserPath)) { - return AstVisitAction.Continue; + return; } if (firstConvertExpression is not null) @@ -5602,22 +5737,22 @@ public override AstVisitAction VisitAssignmentStatement(AssignmentStatementAst a } else { - PSTypeName lastAssignedType = assignmentStatementAst.Right is CommandExpressionAst commandExpression + PSTypeName lastAssignedType = right is CommandExpressionAst commandExpression ? GetInferredVarTypeFromAst(commandExpression.Expression) : null; SaveVariableInfo(variableExpression.VariablePath.UnqualifiedPath, lastAssignedType, isConstraint: false); } } } - else if (assignmentStatementAst.Left is VariableExpressionAst variableExpression) + else if (left is VariableExpressionAst variableExpression) { if (variableExpression == CompletionVariableAst || s_specialVariablesCache.Value.Contains(variableExpression.VariablePath.UserPath)) { - return AstVisitAction.Continue; + return; } PSTypeName lastAssignedType; - if (assignmentStatementAst.Right is CommandExpressionAst commandExpression) + if (right is CommandExpressionAst commandExpression) { lastAssignedType = GetInferredVarTypeFromAst(commandExpression.Expression); } @@ -5628,8 +5763,21 @@ public override AstVisitAction VisitAssignmentStatement(AssignmentStatementAst a SaveVariableInfo(variableExpression.VariablePath.UnqualifiedPath, lastAssignedType, isConstraint: false); } - - return AstVisitAction.Continue; + else if (left is ArrayLiteralAst array) + { + foreach (ExpressionAst expression in array.Elements) + { + ProcessAssignmentLeftSide(expression, right); + } + } + else if (left is ParenExpressionAst parenExpression) + { + ExpressionAst pureExpression = parenExpression.Pipeline.GetPureExpression(); + if (pureExpression is not null) + { + ProcessAssignmentLeftSide(pureExpression, right); + } + } } public override AstVisitAction VisitCommand(CommandAst commandAst) @@ -5939,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)) @@ -6455,7 +6603,9 @@ private static List<CompletionResult> CompleteCommentParameterValue(CompletionCo new List<Tuple<string, string>> { new Tuple<string, string>("Where", "Where({ expression } [, mode [, numberToReturn]])"), - new Tuple<string, string>("ForEach", "ForEach(expression [, arguments...])") + new Tuple<string, string>("ForEach", "ForEach(expression [, arguments...])"), + new Tuple<string, string>("PSWhere", "PSWhere({ expression } [, mode [, numberToReturn]])"), + new Tuple<string, string>("PSForEach", "PSForEach(expression [, arguments...])"), }; // List of DSC collection-value variables @@ -8438,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) @@ -8506,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); @@ -8566,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; } @@ -8632,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 bd44185b460..edf31e8e97a 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionHelpers.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionHelpers.cs @@ -14,43 +14,157 @@ internal static class CompletionHelpers { private static readonly SearchValues<char> s_defaultCharsToCheck = SearchValues.Create("$`"); + private const string SingleQuote = "'"; + private const string DoubleQuote = "\""; + /// <summary> /// Get matching completions from word to complete. /// This makes it easier to handle different variations of completions with consideration of quotes. /// </summary> /// <param name="wordToComplete">The word to complete.</param> /// <param name="possibleCompletionValues">The possible completion values to iterate.</param> - /// <param name="toolTipMapping">The optional tool tip mapping delegate.</param> + /// <param name="displayInfoMapper">The optional completion display info mapper delegate for tool tip and list item text.</param> /// <param name="resultType">The optional completion result type. Default is Text.</param> - /// <returns></returns> + /// <param name="matchStrategy">The optional match strategy delegate.</param> + /// <returns>List of matching completion results.</returns> internal static IEnumerable<CompletionResult> GetMatchingResults( string wordToComplete, IEnumerable<string> possibleCompletionValues, - Func<string, string> toolTipMapping = null, - CompletionResultType resultType = CompletionResultType.Text) + CompletionDisplayInfoMapper displayInfoMapper = null, + CompletionResultType resultType = CompletionResultType.Text, + MatchStrategy matchStrategy = null) { + displayInfoMapper ??= DefaultDisplayInfoMapper; + matchStrategy ??= DefaultMatch; + string quote = HandleDoubleAndSingleQuote(ref wordToComplete); - var pattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase); + if (quote != SingleQuote) + { + wordToComplete = NormalizeToExpandableString(wordToComplete); + } foreach (string value in possibleCompletionValues) { - if (pattern.IsMatch(value)) + if (matchStrategy(value, wordToComplete)) { string completionText = QuoteCompletionText(value, quote); - string listItemText = value; + (string toolTip, string listItemText) = displayInfoMapper(value); - yield return new CompletionResult( - completionText, - listItemText, - resultType, - toolTip: toolTipMapping is null - ? listItemText - : toolTipMapping(value)); + yield return new CompletionResult(completionText, listItemText, resultType, toolTip); } } } + /// <summary> + /// Provides the display information for a completion result. + /// This delegate is used to map a string value to its corresponding display information. + /// </summary> + /// <param name="value">The input value to be mapped</param> + /// <returns>Completion display info containing tool tip and list item text.</returns> + internal delegate (string ToolTip, string ListItemText) CompletionDisplayInfoMapper(string value); + + /// <summary> + /// Provides the default display information for a completion result. + /// Defaults to using the input value for both the tool tip and list item text. + /// </summary> + /// <returns>Completion display info containing tool tip and list item text.</returns> + internal static readonly CompletionDisplayInfoMapper DefaultDisplayInfoMapper = value + => (value, value); + + /// <summary> + /// Normalizes the input string to an expandable string format for PowerShell. + /// </summary> + /// <param name="value">The input string to be normalized.</param> + /// <returns>The normalized string with special characters replaced by their PowerShell escape sequences.</returns> + /// <remarks> + /// This method replaces special characters in the input string with their PowerShell equivalent escape sequences: + /// <list type="bullet"> + /// <item><description>Replaces "\r" (carriage return) with "`r".</description></item> + /// <item><description>Replaces "\n" (newline) with "`n".</description></item> + /// <item><description>Replaces "\t" (tab) with "`t".</description></item> + /// <item><description>Replaces "\0" (null) with "`0".</description></item> + /// <item><description>Replaces "\a" (bell) with "`a".</description></item> + /// <item><description>Replaces "\b" (backspace) with "`b".</description></item> + /// <item><description>Replaces "\u001b" (escape character) with "`e".</description></item> + /// <item><description>Replaces "\f" (form feed) with "`f".</description></item> + /// <item><description>Replaces "\v" (vertical tab) with "`v".</description></item> + /// </list> + /// </remarks> + internal static string NormalizeToExpandableString(string value) + => value + .Replace("\r", "`r") + .Replace("\n", "`n") + .Replace("\t", "`t") + .Replace("\0", "`0") + .Replace("\a", "`a") + .Replace("\b", "`b") + .Replace("\u001b", "`e") + .Replace("\f", "`f") + .Replace("\v", "`v"); + + /// <summary> + /// Defines a strategy for determining if a value matches a word or pattern. + /// </summary> + /// <param name="value">The input string to check for a match.</param> + /// <param name="wordToComplete">The word or pattern to match against.</param> + /// <returns> + /// <c>true</c> if the value matches the specified word or pattern; otherwise, <c>false</c>. + /// </returns> + internal delegate bool MatchStrategy(string value, string wordToComplete); + + /// <summary> + /// Determines if the given value matches the specified word using a literal, case-insensitive prefix match. + /// </summary> + /// <returns> + /// <c>true</c> if the value starts with the word (case-insensitively); otherwise, <c>false</c>. + /// </returns> + internal static readonly MatchStrategy LiteralMatchOrdinalIgnoreCase = (value, wordToComplete) + => value.StartsWith(wordToComplete, StringComparison.OrdinalIgnoreCase); + + /// <summary> + /// Determines if the given value matches the specified word using wildcard pattern matching. + /// </summary> + /// <returns> + /// <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 + /// multiple characters in the input. This strategy is case-insensitive. + /// </remarks> + internal static readonly MatchStrategy WildcardPatternMatchIgnoreCase = (value, wordToComplete) + => WildcardPattern + .Get(wordToComplete + "*", WildcardOptions.IgnoreCase) + .IsMatch(value); + + /// <summary> + /// 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; + /// 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 + /// handle any problematic wildcard characters before performing a wildcard match. + /// </remarks> + internal static readonly MatchStrategy WildcardPatternEscapeMatch = (value, wordToComplete) + => LiteralMatchOrdinalIgnoreCase(value, wordToComplete) || + WildcardPatternMatchIgnoreCase(value, WildcardPattern.Escape(wordToComplete)); + + /// <summary> + /// Determines if the given value matches the specified word taking into account wildcard characters. + /// </summary> + /// <returns> + /// <c>true</c> if the value matches either the literal normalized word or the wildcard pattern; otherwise, <c>false</c>. + /// </returns> + /// <remarks> + /// This strategy attempts a literal match first for performance and, if unsuccessful, evaluates the word against a wildcard pattern. + /// </remarks> + internal static readonly MatchStrategy DefaultMatch = (value, wordToComplete) + => LiteralMatchOrdinalIgnoreCase(value, wordToComplete) || + WildcardPatternMatchIgnoreCase(value, wordToComplete); + /// <summary> /// Removes wrapping quotes from a string and returns the quote used, if present. /// </summary> @@ -83,7 +197,7 @@ internal static string HandleDoubleAndSingleQuote(ref string wordToComplete) return string.Empty; } - string quoteInUse = hasFrontSingleQuote ? "'" : "\""; + string quoteInUse = hasFrontSingleQuote ? SingleQuote : DoubleQuote; int length = wordToComplete.Length; if (length == 1) @@ -105,7 +219,7 @@ internal static string HandleDoubleAndSingleQuote(ref string wordToComplete) return quoteInUse; } - bool hasFrontQuoteAndNoBackQuote = + bool hasFrontQuoteAndNoBackQuote = (hasFrontSingleQuote || hasFrontDoubleQuote) && !hasBackSingleQuote && !hasBackDoubleQuote; if (hasFrontQuoteAndNoBackQuote) @@ -124,6 +238,7 @@ internal static string HandleDoubleAndSingleQuote(ref string wordToComplete) /// <item><description>There are parsing errors in the input string.</description></item> /// <item><description>The parsed token count is not exactly two (the input token + EOF).</description></item> /// <item><description>The first token is a string or a PowerShell keyword containing special characters.</description></item> + /// <item><description>The first token is a semi colon or comma token.</description></item> /// </list> /// </summary> /// <param name="completion">The input string to analyze for quoting requirements.</param> @@ -139,15 +254,33 @@ internal static bool CompletionRequiresQuotes(string completion) Token firstToken = tokens[0]; bool isStringToken = firstToken is StringToken; bool isKeywordToken = (firstToken.TokenFlags & TokenFlags.Keyword) != 0; + bool isSemiToken = firstToken.Kind == TokenKind.Semi; + bool isCommaToken = firstToken.Kind == TokenKind.Comma; if ((!requireQuote && isStringToken) || (isExpectedTokenCount && isKeywordToken)) { requireQuote = ContainsCharsToCheck(firstToken.Text); } + else if (isExpectedTokenCount && (isSemiToken || isCommaToken)) + { + requireQuote = true; + } + return requireQuote; } + /// <summary> + /// Determines whether the given text contains an escaped newline string. + /// </summary> + /// <param name="text">The input string to check for escaped newlines.</param> + /// <returns> + /// <c>true</c> if the text contains the escaped Unix-style newline string ("`n") or + /// the Windows-style newline string ("`r`n"); otherwise, <c>false</c>. + /// </returns> + private static bool ContainsEscapedNewlineString(string text) + => text.Contains("`n", StringComparison.Ordinal); + private static bool ContainsCharsToCheck(ReadOnlySpan<char> text) => text.ContainsAny(s_defaultCharsToCheck); @@ -165,14 +298,20 @@ private static bool ContainsCharsToCheck(ReadOnlySpan<char> text) /// </returns> internal static string QuoteCompletionText(string completionText, string quote) { + // Escaped newlines e.g. `r`n need be surrounded with double quotes + if (ContainsEscapedNewlineString(completionText)) + { + return DoubleQuote + completionText + DoubleQuote; + } + if (!CompletionRequiresQuotes(completionText)) { return quote + completionText + quote; } - string quoteInUse = string.IsNullOrEmpty(quote) ? "'" : quote; + string quoteInUse = string.IsNullOrEmpty(quote) ? SingleQuote : quote; - if (quoteInUse == "'") + if (quoteInUse == SingleQuote) { completionText = CodeGeneration.EscapeSingleQuotedStringContent(completionText); } 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 6183f98a0aa..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; } @@ -2835,7 +2835,7 @@ internal PropertyCacheEntry(PropertyInfo property) // Get the public or protected getter MethodInfo propertyGetter = property.GetGetMethod(true); - if (propertyGetter != null && (propertyGetter.IsPublic || propertyGetter.IsFamily)) + if (propertyGetter != null && (propertyGetter.IsPublic || propertyGetter.IsFamily || propertyGetter.IsFamilyOrAssembly)) { this.isStatic = propertyGetter.IsStatic; // Delegate is initialized later to avoid jit if it's not called @@ -2847,7 +2847,7 @@ internal PropertyCacheEntry(PropertyInfo property) // Get the public or protected setter MethodInfo propertySetter = property.GetSetMethod(true); - if (propertySetter != null && (propertySetter.IsPublic || propertySetter.IsFamily)) + if (propertySetter != null && (propertySetter.IsPublic || propertySetter.IsFamily || propertySetter.IsFamilyOrAssembly)) { this.isStatic = propertySetter.IsStatic; } @@ -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/ExecutionContext.cs b/src/System.Management.Automation/engine/ExecutionContext.cs index 16f2dbcc218..ac39eade17b 100644 --- a/src/System.Management.Automation/engine/ExecutionContext.cs +++ b/src/System.Management.Automation/engine/ExecutionContext.cs @@ -1385,6 +1385,7 @@ private static Assembly LoadAssembly(string name, string filePath, out Exception { try { + // codeql[cs/dll-injection-remote] - The dll is loaded during the initial state setup, which is expected behavior. This allows users hosting PowerShell to load additional C# types to enable their specific scenarios. loadedAssembly = Assembly.LoadFrom(filePath); return loadedAssembly; } 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 ed1dc189270..13d8f66e5e9 100644 --- a/src/System.Management.Automation/engine/LanguagePrimitives.cs +++ b/src/System.Management.Automation/engine/LanguagePrimitives.cs @@ -202,7 +202,6 @@ public override object ConvertFrom(object sourceValue, Type destinationType, IFo string sourceAsString = (string)LanguagePrimitives.ConvertTo(sourceValue, typeof(string), formatProvider); return LanguagePrimitives.ConvertTo(sourceAsString, destinationType, formatProvider); } - /// <summary> /// Returns false, since this converter is not designed to be used to /// convert from the type associated with the converted to other types. @@ -637,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)); } @@ -781,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)); } @@ -1037,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]); } @@ -2101,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,17 +2942,12 @@ private static object ConvertStringToInteger( return result; } - if (resultType == typeof(BigInteger)) - { - // Fallback for BigInteger: manual parsing using any common format. - NumberStyles style = NumberStyles.AllowLeadingSign - | NumberStyles.AllowDecimalPoint - | NumberStyles.AllowExponent - | NumberStyles.AllowHexSpecifier; + if (resultType == typeof(BigInteger)) + { + NumberStyles style = NumberStyles.Integer | NumberStyles.AllowThousands; return BigInteger.Parse(strToConvert, style, NumberFormatInfo.InvariantInfo); } - // Fallback conversion for regular numeric types. return GetIntegerSystemConverter(resultType).ConvertFrom(strToConvert); } 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 105704fb256..0a1d0bfc04f 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs @@ -136,12 +136,17 @@ protected internal struct ImportModuleOptions internal SessionState TargetSessionState { - // Module loading could happen during tab completion triggered by PSReadLine, - // but that doesn't mean the module should be loaded targeting the PSReadLine - // module's session state. In that case, use Global session state instead. - get => BaseGlobal || Context.EngineSessionState.Module?.Name is "PSReadLine" - ? Context.TopLevelSessionState.PublicSessionState - : Context.SessionState; + get + { + if (BaseGlobal) + { + return this.Context.TopLevelSessionState.PublicSessionState; + } + else + { + return this.Context.SessionState; + } + } } /// <summary> @@ -1620,7 +1625,17 @@ internal PSModuleInfo LoadModuleManifest( if (bailOnFirstError) return null; } - string resolvedCommandPrefix = BasePrefix ?? defaultCommandPrefix ?? string.Empty; + string resolvedCommandPrefix = string.Empty; + + if (!string.IsNullOrEmpty(defaultCommandPrefix)) + { + resolvedCommandPrefix = defaultCommandPrefix; + } + + if (!string.IsNullOrEmpty(this.BasePrefix)) + { + resolvedCommandPrefix = this.BasePrefix; + } if (!string.IsNullOrEmpty(actualRootModule)) { @@ -6153,7 +6168,7 @@ private void CheckForDisallowedDotSourcing( } } } - + private static void RemoveNestedModuleFunctions( ExecutionContext context, PSModuleInfo module, @@ -7261,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 9d59d02cb30..e674cb1c5eb 100644 --- a/src/System.Management.Automation/engine/MshCommandRuntime.cs +++ b/src/System.Management.Automation/engine/MshCommandRuntime.cs @@ -390,6 +390,9 @@ public void WriteProgress( WriteProgress(sourceId, progressRecord, false); } + internal bool IsWriteProgressEnabled() + => WriteHelper_ShouldWrite(ProgressPreference, lastProgressContinueStatus); + internal void WriteProgress( Int64 sourceId, ProgressRecord progressRecord, @@ -472,6 +475,9 @@ public void WriteDebug(string text) WriteDebug(new DebugRecord(text)); } + internal bool IsWriteDebugEnabled() + => WriteHelper_ShouldWrite(DebugPreference, lastDebugContinueStatus); + /// <summary> /// Display debug information. /// </summary> @@ -566,6 +572,9 @@ public void WriteVerbose(string text) WriteVerbose(new VerboseRecord(text)); } + internal bool IsWriteVerboseEnabled() + => WriteHelper_ShouldWrite(VerbosePreference, lastVerboseContinueStatus); + /// <summary> /// Display verbose information. /// </summary> @@ -660,6 +669,9 @@ public void WriteWarning(string text) WriteWarning(new WarningRecord(text)); } + internal bool IsWriteWarningEnabled() + => WriteHelper_ShouldWrite(WarningPreference, lastWarningContinueStatus); + /// <summary> /// Display warning information. /// </summary> @@ -733,6 +745,9 @@ public void WriteInformation(InformationRecord informationRecord) WriteInformation(informationRecord, false); } + internal bool IsWriteInformationEnabled() + => WriteHelper_ShouldWrite(InformationPreference, lastInformationContinueStatus); + /// <summary> /// Display tagged object information. /// </summary> @@ -2345,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) @@ -2357,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 @@ -2977,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 371e1ff00ff..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 @@ -133,7 +135,7 @@ internal ProcessOutputObject(object data, MinishellStream stream) } } - #nullable enable +#nullable enable /// <summary> /// This exception is used by the NativeCommandProcessor to indicate an error /// when a native command returns a non-zero exit code. @@ -187,34 +189,218 @@ internal NativeCommandExitException(string path, int exitCode, int processId, st public int ProcessId { get; } } - #nullable restore +#nullable restore /// <summary> /// Provides way to create and execute native commands. /// </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) + { + 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) + { + // 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) { - ".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 (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) { - "cmd", - "cscript", - "find", - "sqlcmd", - "wscript", - }; + 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) { @@ -972,11 +1178,17 @@ internal override void Complete() } const string errorId = nameof(CommandBaseStrings.ProgramExitedWithNonZeroCode); +#if UNIX + string hexFormatStr = "0x{0:X2}"; +#else + string hexFormatStr = "0x{0:X8}"; +#endif string errorMsg = StringUtil.Format( CommandBaseStrings.ProgramExitedWithNonZeroCode, NativeCommandName, - _nativeProcess.ExitCode); + _nativeProcess.ExitCode, + string.Format(CultureInfo.InvariantCulture, hexFormatStr, _nativeProcess.ExitCode)); var exception = new NativeCommandExitException( Path, @@ -1396,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 }; @@ -1433,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; } } @@ -1465,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; } } @@ -1499,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. @@ -1711,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> @@ -2295,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 1438671e1ec..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; } @@ -3497,37 +3497,7 @@ internal void NewItem( throw PSTraceSource.NewArgumentNullException(nameof(content), SessionStateStrings.NewLinkTargetNotSpecified, path); } - ProviderInfo targetProvider = null; - CmdletProvider targetProviderInstance = null; - - var globbedTarget = Globber.GetGlobbedProviderPathsFromMonadPath( - targetPath, - allowNonexistingPath, - context, - out targetProvider, - out targetProviderInstance); - - if (!string.Equals(targetProvider.Name, "filesystem", StringComparison.OrdinalIgnoreCase)) - { - throw PSTraceSource.NewNotSupportedException(SessionStateStrings.MustBeFileSystemPath); - } - - if (globbedTarget.Count > 1) - { - throw PSTraceSource.NewInvalidOperationException(SessionStateStrings.PathResolvedToMultiple, targetPath); - } - - if (globbedTarget.Count == 0) - { - throw PSTraceSource.NewInvalidOperationException(SessionStateStrings.PathNotFound, targetPath); - } - - // If the original target was a relative path, we want to leave it as relative if it did not require - // globbing to resolve. - if (WildcardPattern.ContainsWildcardCharacters(targetPath)) - { - content = globbedTarget[0]; - } + content = targetPath; } NewItemPrivate(providerInstance, composedPath, type, content, context); 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 420b52d4d22..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); @@ -341,6 +345,7 @@ internal static class SpecialVariables SpecialVariables.WarningPreference, SpecialVariables.InformationPreference, SpecialVariables.ConfirmPreference, + SpecialVariables.ProgressPreference, }; internal static readonly Type[] PreferenceVariableTypes = @@ -352,6 +357,7 @@ internal static class SpecialVariables /* WarningPreference */ typeof(ActionPreference), /* InformationPreference */ typeof(ActionPreference), /* ConfirmPreference */ typeof(ConfirmImpact), + /* ProgressPreference */ typeof(ActionPreference), }; // The following variables are created in every session w/ AllScope. We avoid creating local slots when we @@ -386,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/cmdlet.cs b/src/System.Management.Automation/engine/cmdlet.cs index ffe13b7304f..db2233d44a9 100644 --- a/src/System.Management.Automation/engine/cmdlet.cs +++ b/src/System.Management.Automation/engine/cmdlet.cs @@ -458,6 +458,9 @@ public void WriteVerbose(string text) } } + internal bool IsWriteVerboseEnabled() + => commandRuntime is not MshCommandRuntime mshRuntime || mshRuntime.IsWriteVerboseEnabled(); + /// <summary> /// Display warning information. /// </summary> @@ -495,6 +498,9 @@ public void WriteWarning(string text) } } + internal bool IsWriteWarningEnabled() + => commandRuntime is not MshCommandRuntime mshRuntime || mshRuntime.IsWriteWarningEnabled(); + /// <summary> /// Write text into pipeline execution log. /// </summary> @@ -603,6 +609,9 @@ internal void WriteProgress( throw new System.NotImplementedException("WriteProgress"); } + internal bool IsWriteProgressEnabled() + => commandRuntime is not MshCommandRuntime mshRuntime || mshRuntime.IsWriteProgressEnabled(); + /// <summary> /// Display debug information. /// </summary> @@ -646,6 +655,9 @@ public void WriteDebug(string text) } } + internal bool IsWriteDebugEnabled() + => commandRuntime is not MshCommandRuntime mshRuntime || mshRuntime.IsWriteDebugEnabled(); + /// <summary> /// Route information to the user or host. /// </summary> @@ -753,6 +765,9 @@ public void WriteInformation(InformationRecord informationRecord) } } + internal bool IsWriteInformationEnabled() + => commandRuntime is not MshCommandRuntime mshRuntime || mshRuntime.IsWriteInformationEnabled(); + #endregion Write #region ShouldProcess diff --git a/src/System.Management.Automation/engine/debugger/debugger.cs b/src/System.Management.Automation/engine/debugger/debugger.cs index 68b6d1a296e..985d90ab3ec 100644 --- a/src/System.Management.Automation/engine/debugger/debugger.cs +++ b/src/System.Management.Automation/engine/debugger/debugger.cs @@ -1536,7 +1536,16 @@ private List<Breakpoint> TriggerBreakpoints(List<Breakpoint> breakpoints) internal void OnSequencePointHit(FunctionContext functionContext) { - if (_context.ShouldTraceStatement && !_callStack.Last().IsFrameHidden && !functionContext._debuggerStepThrough) + // TraceLine uses ColumnNumber and expects it to be 1 based. For + // extents added by the engine and not user code the value can be + // set to 0 causing an exception. This skips those types of extents + // as tracing them wouldn't be useful for the end user anyway. + if (_context.ShouldTraceStatement && + !_callStack.Last().IsFrameHidden && + !functionContext._debuggerStepThrough && + functionContext.CurrentPosition is not EmptyScriptExtent && + (functionContext.CurrentPosition is InternalScriptExtent || + functionContext.CurrentPosition.StartColumnNumber > 0)) { TraceLine(functionContext.CurrentPosition); } @@ -2075,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; @@ -2090,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. } @@ -2359,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, @@ -3820,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; @@ -4677,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/Command.cs b/src/System.Management.Automation/engine/hostifaces/Command.cs index 6d8cd526486..3de339ff112 100644 --- a/src/System.Management.Automation/engine/hostifaces/Command.cs +++ b/src/System.Management.Automation/engine/hostifaces/Command.cs @@ -662,7 +662,7 @@ internal PSObject ToPSObjectForRemoting(Version psRPVersion) commandAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.MergeUnclaimedPreviousCommandResults, this.MergeUnclaimedPreviousCommandResults)); if (psRPVersion != null && - psRPVersion >= RemotingConstants.ProtocolVersionWin10RTM) + psRPVersion >= RemotingConstants.ProtocolVersion_2_3) { // V5 merge instructions commandAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.MergeError, MergeInstructions[(int)MergeType.Error])); @@ -672,7 +672,7 @@ internal PSObject ToPSObjectForRemoting(Version psRPVersion) commandAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.MergeInformation, MergeInstructions[(int)MergeType.Information])); } else if (psRPVersion != null && - psRPVersion >= RemotingConstants.ProtocolVersionWin8RTM) + psRPVersion >= RemotingConstants.ProtocolVersion_2_2) { // V3 merge instructions. commandAsPSObject.Properties.Add(new PSNoteProperty(RemoteDataNameStrings.MergeError, MergeInstructions[(int)MergeType.Error])); 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/InternalHostUserInterface.cs b/src/System.Management.Automation/engine/hostifaces/InternalHostUserInterface.cs index 94a25726f7b..d8f2ef0bf90 100644 --- a/src/System.Management.Automation/engine/hostifaces/InternalHostUserInterface.cs +++ b/src/System.Management.Automation/engine/hostifaces/InternalHostUserInterface.cs @@ -209,7 +209,14 @@ public override return; } - _externalUI.Write(foregroundColor, backgroundColor, value); + if (PSStyle.Instance.OutputRendering == OutputRendering.PlainText) + { + _externalUI.Write(value); + } + else + { + _externalUI.Write(foregroundColor, backgroundColor, value); + } } /// <summary> @@ -303,7 +310,14 @@ public override return; } - _externalUI.WriteLine(foregroundColor, backgroundColor, value); + if (PSStyle.Instance.OutputRendering == OutputRendering.PlainText) + { + _externalUI.WriteLine(value); + } + else + { + _externalUI.WriteLine(foregroundColor, backgroundColor, value); + } } /// <summary> 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 503d8e075b6..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> @@ -5271,7 +5253,7 @@ private bool ServerSupportsBatchInvocation() if (_runspace != null) { return _runspace.RunspaceStateInfo.State != RunspaceState.BeforeOpen && - _runspace.GetRemoteProtocolVersion() >= RemotingConstants.ProtocolVersionWin8RTM; + _runspace.GetRemoteProtocolVersion() >= RemotingConstants.ProtocolVersion_2_2; } RemoteRunspacePoolInternal remoteRunspacePoolInternal = null; @@ -5285,7 +5267,7 @@ private bool ServerSupportsBatchInvocation() } return remoteRunspacePoolInternal != null && - remoteRunspacePoolInternal.PSRemotingProtocolVersion >= RemotingConstants.ProtocolVersionWin8RTM; + remoteRunspacePoolInternal.PSRemotingProtocolVersion >= RemotingConstants.ProtocolVersion_2_2; } /// <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/Compiler.cs b/src/System.Management.Automation/engine/parser/Compiler.cs index 30528f99588..5ce6a6c6dc8 100644 --- a/src/System.Management.Automation/engine/parser/Compiler.cs +++ b/src/System.Management.Automation/engine/parser/Compiler.cs @@ -477,6 +477,9 @@ internal static class CachedReflectionInfo internal static readonly MethodInfo PSSetMemberBinder_SetAdaptedValue = typeof(PSSetMemberBinder).GetMethod(nameof(PSSetMemberBinder.SetAdaptedValue), StaticFlags); + internal static readonly MethodInfo PSTraceSource_WriteLine = + typeof(PSTraceSource).GetMethod(nameof(PSTraceSource.WriteLine), InstanceFlags, new[] { typeof(string), typeof(object) }); + internal static readonly MethodInfo PSVariableAssignmentBinder_CopyInstanceMembersOfValueType = typeof(PSVariableAssignmentBinder).GetMethod(nameof(PSVariableAssignmentBinder.CopyInstanceMembersOfValueType), StaticFlags); @@ -833,6 +836,14 @@ internal class Compiler : ICustomAstVisitor2 static Compiler() { + Diagnostics.Assert(SpecialVariables.AutomaticVariables.Length == (int)AutomaticVariable.NumberOfAutomaticVariables + && SpecialVariables.AutomaticVariableTypes.Length == (int)AutomaticVariable.NumberOfAutomaticVariables, + "The 'AutomaticVariable' enum length does not match both 'AutomaticVariables' and 'AutomaticVariableTypes' length."); + + Diagnostics.Assert(Enum.GetNames(typeof(PreferenceVariable)).Length == SpecialVariables.PreferenceVariables.Length + && Enum.GetNames(typeof(PreferenceVariable)).Length == SpecialVariables.PreferenceVariableTypes.Length, + "The 'PreferenceVariable' enum length does not match both 'PreferenceVariables' and 'PreferenceVariableTypes' length."); + s_functionContext = Expression.Parameter(typeof(FunctionContext), "funcContext"); s_executionContextParameter = Expression.Variable(typeof(ExecutionContext), "context"); diff --git a/src/System.Management.Automation/engine/parser/Position.cs b/src/System.Management.Automation/engine/parser/Position.cs index 589c3eb92a5..9e1363e4a53 100644 --- a/src/System.Management.Automation/engine/parser/Position.cs +++ b/src/System.Management.Automation/engine/parser/Position.cs @@ -338,10 +338,7 @@ internal static bool IsAfter(this IScriptExtent extentToTest, IScriptExtent endE internal static bool IsWithin(this IScriptExtent extentToTest, IScriptExtent extent) { - return extentToTest.StartLineNumber >= extent.StartLineNumber && - extentToTest.EndLineNumber <= extent.EndLineNumber && - extentToTest.StartColumnNumber >= extent.StartColumnNumber && - extentToTest.EndColumnNumber <= extent.EndColumnNumber; + return extentToTest.StartOffset >= extent.StartOffset && extentToTest.EndOffset <= extent.EndOffset; } internal static bool IsAfter(this IScriptExtent extent, int line, int column) 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 167b4219147..983180bd5ca 100644 --- a/src/System.Management.Automation/engine/remoting/client/RemoteRunspacePoolInternal.cs +++ b/src/System.Management.Automation/engine/remoting/client/RemoteRunspacePoolInternal.cs @@ -291,7 +291,7 @@ internal override bool ResetRunspaceState() // version 2.3 or greater. Version remoteProtocolVersionDeclaredByServer = PSRemotingProtocolVersion; if ((remoteProtocolVersionDeclaredByServer == null) || - (remoteProtocolVersionDeclaredByServer < RemotingConstants.ProtocolVersionWin10RTM)) + (remoteProtocolVersionDeclaredByServer < RemotingConstants.ProtocolVersion_2_3)) { throw PSTraceSource.NewInvalidOperationException(RunspacePoolStrings.ResetRunspaceStateNotSupportedOnServer); } @@ -733,7 +733,7 @@ internal bool CanDisconnect { // Disconnect/Connect support is currently only provided by the WSMan transport // that is running PSRP protocol version 2.2 and greater. - return (remoteProtocolVersionDeclaredByServer >= RemotingConstants.ProtocolVersionWin8RTM && + return (remoteProtocolVersionDeclaredByServer >= RemotingConstants.ProtocolVersion_2_2 && DataStructureHandler.EndpointSupportsDisconnect); } @@ -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/RunspaceRef.cs b/src/System.Management.Automation/engine/remoting/client/RunspaceRef.cs index f76b836885a..91cf4161a99 100644 --- a/src/System.Management.Automation/engine/remoting/client/RunspaceRef.cs +++ b/src/System.Management.Automation/engine/remoting/client/RunspaceRef.cs @@ -326,7 +326,7 @@ internal void Override(RemoteRunspace remoteRunspace, object syncObject, out boo powerShell.AddParameter("Name", new string[] { "Out-Default", "Exit-PSSession" }); powerShell.Runspace = _runspaceRef.Value; - bool isReleaseCandidateBackcompatibilityMode = _runspaceRef.Value.GetRemoteProtocolVersion() == RemotingConstants.ProtocolVersionWin7RC; + bool isReleaseCandidateBackcompatibilityMode = _runspaceRef.Value.GetRemoteProtocolVersion() == RemotingConstants.ProtocolVersion_2_0; powerShell.IsGetCommandMetadataSpecialPipeline = !isReleaseCandidateBackcompatibilityMode; int expectedNumberOfResults = isReleaseCandidateBackcompatibilityMode ? 2 : 3; diff --git a/src/System.Management.Automation/engine/remoting/client/clientremotesession.cs b/src/System.Management.Automation/engine/remoting/client/clientremotesession.cs index efcab242bb6..499062d7a18 100644 --- a/src/System.Management.Automation/engine/remoting/client/clientremotesession.cs +++ b/src/System.Management.Automation/engine/remoting/client/clientremotesession.cs @@ -505,20 +505,11 @@ private bool RunClientNegotiationAlgorithm(RemoteSessionCapability serverRemoteS _serverProtocolVersion = serverProtocolVersion; Version clientProtocolVersion = Context.ClientCapability.ProtocolVersion; - if ( - clientProtocolVersion.Equals(serverProtocolVersion) - || (clientProtocolVersion == RemotingConstants.ProtocolVersionWin7RTM && - serverProtocolVersion == RemotingConstants.ProtocolVersionWin7RC) - || (clientProtocolVersion == RemotingConstants.ProtocolVersionWin8RTM && - (serverProtocolVersion == RemotingConstants.ProtocolVersionWin7RC || - serverProtocolVersion == RemotingConstants.ProtocolVersionWin7RTM - )) - || (clientProtocolVersion == RemotingConstants.ProtocolVersionWin10RTM && - (serverProtocolVersion == RemotingConstants.ProtocolVersionWin7RC || - serverProtocolVersion == RemotingConstants.ProtocolVersionWin7RTM || - serverProtocolVersion == RemotingConstants.ProtocolVersionWin8RTM - )) - ) + if (clientProtocolVersion == serverProtocolVersion || + serverProtocolVersion == RemotingConstants.ProtocolVersion_2_0 || + serverProtocolVersion == RemotingConstants.ProtocolVersion_2_1 || + serverProtocolVersion == RemotingConstants.ProtocolVersion_2_2 || + serverProtocolVersion == RemotingConstants.ProtocolVersion_2_3) { // passed negotiation check } 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 534362b0f17..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 @@ -1026,7 +1026,7 @@ protected override void BeginProcessing() { // In order to support foreach remoting properly ( icm | % { icm } ), the server must // be using protocol version 2.2. Otherwise, we skip this and assume the old behavior. - if (version >= RemotingConstants.ProtocolVersionWin8RTM) + if (version >= RemotingConstants.ProtocolVersion_2_2) { // Suppress collection behavior _needToCollect = false; @@ -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 5988884d16b..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> @@ -1923,9 +1923,7 @@ internal Pipeline CreatePipeline(RemoteRunspace remoteRunspace) // the array-form using values if all UsingExpressions are in the same scope, otherwise, we handle the UsingExpression as // if the remote end is PSv2. string serverPsVersion = GetRemoteServerPsVersion(remoteRunspace); - System.Management.Automation.PowerShell powershellToUse = (serverPsVersion == PSv2) - ? GetPowerShellForPSv2() - : GetPowerShellForPSv3OrLater(serverPsVersion); + System.Management.Automation.PowerShell powershellToUse = GetPowerShellForPSv3OrLater(serverPsVersion); Pipeline pipeline = remoteRunspace.CreatePipeline(powershellToUse.Commands.Commands[0].CommandText, true); @@ -1946,9 +1944,9 @@ internal Pipeline CreatePipeline(RemoteRunspace remoteRunspace) /// </summary> private static string GetRemoteServerPsVersion(RemoteRunspace remoteRunspace) { - if (remoteRunspace.ConnectionInfo is NewProcessConnectionInfo) + if (remoteRunspace.ConnectionInfo is not WSManConnectionInfo) { - // This is for Start-Job. The remote end is actually a child local powershell process, so it must be PSv5 or later + // All transport types except for WSManConnectionInfo work with 5.1 or later. return PSv5OrLater; } @@ -1957,35 +1955,19 @@ private static string GetRemoteServerPsVersion(RemoteRunspace remoteRunspace) { // The remote runspace is not opened yet, or it's disconnected before the private data is retrieved. // In this case we cannot validate if the remote server is running PSv5 or later, so for safety purpose, - // we will handle the $using expressions as if the remote server is PSv2. - return PSv2; + // we will handle the $using expressions as if the remote server is PSv3Orv4. + return PSv3Orv4; } - // Unfortunately, the PSVersion value in the private data from PSv3 and PSv4 server is always 2.0. - // This got fixed in PSv5, so a PSv5 server will return 5.0. That means we need other way to tell - // if the remote server is PSv2 or PSv3+. After PSv3, remote runspace supports connect/disconnect, - // so we can use it to differentiate PSv2 from PSv3+. - if (remoteRunspace.CanDisconnect) - { - Version serverPsVersion = null; - PSPrimitiveDictionary.TryPathGet( - psApplicationPrivateData, - out serverPsVersion, - PSVersionInfo.PSVersionTableName, - PSVersionInfo.PSVersionName); - - if (serverPsVersion != null) - { - return serverPsVersion.Major >= 5 ? PSv5OrLater : PSv3Orv4; - } - - // The private data is available but we failed to get the server powershell version. - // This should never happen, but in case it happens, handle the $using expressions - // as if the remote server is PSv2. - Dbg.Assert(false, "Application private data is available but we failed to get the server powershell version. This should never happen."); - } + PSPrimitiveDictionary.TryPathGet( + psApplicationPrivateData, + out Version serverPsVersion, + PSVersionInfo.PSVersionTableName, + PSVersionInfo.PSVersionName); - return PSv2; + // PSv5 server will return 5.0 whereas older versions will always be 2.0. As we don't care about v2 + // anymore we can use a simple ternary check here to differenciate v5 using behaviour vs v3/4. + return serverPsVersion != null && serverPsVersion.Major >= 5 ? PSv5OrLater : PSv3Orv4; } /// <summary> @@ -2059,7 +2041,6 @@ private void WriteErrorCreateRemoteRunspaceFailed(Exception e, Uri uri) /// </summary> private const string PSv5OrLater = "PSv5OrLater"; private const string PSv3Orv4 = "PSv3Orv4"; - private const string PSv2 = "PSv2"; private System.Management.Automation.PowerShell _powershellV2; private System.Management.Automation.PowerShell _powershellV3; @@ -4168,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 e80c51fb816..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; @@ -454,7 +453,7 @@ private static NamedPipeServerStream CreateNamedPipe( SafePipeHandle pipeHandle = NamedPipeNative.CreateNamedPipe( fullPipeName, NamedPipeNative.PIPE_ACCESS_DUPLEX | NamedPipeNative.FILE_FLAG_FIRST_PIPE_INSTANCE | NamedPipeNative.FILE_FLAG_OVERLAPPED, - NamedPipeNative.PIPE_TYPE_MESSAGE | NamedPipeNative.PIPE_READMODE_MESSAGE, + NamedPipeNative.PIPE_TYPE_MESSAGE | NamedPipeNative.PIPE_READMODE_MESSAGE | NamedPipeNative.PIPE_REJECT_REMOTE_CLIENTS, 1, _namedPipeBufferSizeForRemoting, _namedPipeBufferSizeForRemoting, @@ -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 9c221f01dbb..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; } @@ -2206,14 +2206,56 @@ internal int StartSSHProcess( var context = Runspaces.LocalPipeline.GetExecutionContextFromTLS(); if (context != null) { - var cmdInfo = context.CommandDiscovery.LookupCommandInfo(sshCommand, CommandOrigin.Internal) as ApplicationInfo; - if (cmdInfo != null) + var cmdInfo = CommandDiscovery.LookupCommandInfo( + sshCommand, + CommandTypes.Application, + SearchResolutionOptions.None, + CommandOrigin.Internal, + context); + + if (cmdInfo is ApplicationInfo appInfo) + { + filePath = appInfo.Path; + } + } + else + { + // A Runspace may not be present in the TLS in SDK hosted apps + // or if running in another thread without a Runspace. While + // 'ProcessStartInfo' can lookup the full path in PATH, it searches + // the process' working directory first. 'LookupCommandInfo' does + // not search the process' working directory and we want to keep that + // behavior. We also get the parent dir of the full path to set as the + // new WorkingDirectory. So, we do a manual lookup here only in PATH. + string[] entries = Environment.GetEnvironmentVariable("PATH")?.Split( + Path.PathSeparator, + StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) ?? []; + foreach (var path in entries) { - filePath = cmdInfo.Path; + if (!Path.IsPathFullyQualified(path)) + { + continue; + } + + var sshCommandPath = Path.Combine(path, sshCommand); + if (File.Exists(sshCommandPath)) + { + filePath = sshCommandPath; + break; + } } } + + if (string.IsNullOrEmpty(filePath)) + { + throw new CommandNotFoundException( + sshCommand, + null, + "CommandNotFoundException", + 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: @@ -2230,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)); @@ -2283,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; @@ -2462,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; ) { @@ -2537,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. @@ -2554,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 @@ -2598,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 @@ -2625,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) || @@ -2650,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; } @@ -2662,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) { @@ -2693,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 { @@ -2786,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 70693c4c3d9..e26f3421cda 100644 --- a/src/System.Management.Automation/engine/remoting/common/WireDataFormat/EncodeAndDecode.cs +++ b/src/System.Management.Automation/engine/remoting/common/WireDataFormat/EncodeAndDecode.cs @@ -76,10 +76,11 @@ internal static class RemotingConstants { internal static readonly Version HostVersion = PSVersionInfo.PSVersion; - internal static readonly Version ProtocolVersionWin7RC = new Version(2, 0); - internal static readonly Version ProtocolVersionWin7RTM = new Version(2, 1); - internal static readonly Version ProtocolVersionWin8RTM = new Version(2, 2); - internal static readonly Version ProtocolVersionWin10RTM = new Version(2, 3); + internal static readonly Version ProtocolVersion_2_0 = new(2, 0); // Window 7 RC + internal static readonly Version ProtocolVersion_2_1 = new(2, 1); // Window 7 RTM + internal static readonly Version ProtocolVersion_2_2 = new(2, 2); // Window 8 RTM + internal static readonly Version ProtocolVersion_2_3 = new(2, 3); // Window 10 RTM + internal static readonly Version ProtocolVersion_2_4 = new(2, 4); // PowerShell 7.6 // Minor will be incremented for each change in PSRP client/server stack and new versions will be // forked on early major release/drop changes history. @@ -87,7 +88,15 @@ internal static class RemotingConstants // 2.102 to 2.103 - Key exchange protocol changes in M3 // 2.103 to 2.2 - Final ship protocol version value, no change to protocol // 2.2 to 2.3 - Enabling informational stream - internal static readonly Version ProtocolVersionCurrent = new Version(2, 3); + // 2.3 to 2.4 - Deprecate the 'Session_Key' exchange. The following messages are obsolete when both server and client are v2.4+: + // - PUBLIC_KEY + // - PUBLIC_KEY_REQUEST + // - ENCRYPTED_SESSION_KEY + // The padding algorithm 'RSAEncryptionPadding.Pkcs1' used in the 'Session_Key' exchange is NOT secure, and therefore, + // PSRP needs to be used on top of a secure transport and the 'Session_Key' doesn't add any extra security. + // So, we decided to deprecate the 'Session_Key' exchange in PSRP and skip encryption and decryption for 'SecureString' + // objects. Instead, we require the transport to be secure for secure data transfer between PSRP clients and servers. + internal static readonly Version ProtocolVersionCurrent = new(2, 4); internal static readonly Version ProtocolVersion = ProtocolVersionCurrent; // Used by remoting commands to add remoting specific note properties. internal static readonly string ComputerNameNoteProperty = "PSComputerName"; @@ -1860,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); @@ -2128,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); @@ -2158,7 +2167,7 @@ internal static bool ServerSupportsBatchInvocation(Runspace runspace) return false; } - return (runspace.GetRemoteProtocolVersion() >= RemotingConstants.ProtocolVersionWin8RTM); + return (runspace.GetRemoteProtocolVersion() >= RemotingConstants.ProtocolVersion_2_2); } } } 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 6ff9187d3bf..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; @@ -1258,7 +1253,7 @@ public override void CloseAsync() /// <param name="serverProtocolVersion">Server negotiated protocol version.</param> internal void AdjustForProtocolVariations(Version serverProtocolVersion) { - if (serverProtocolVersion <= RemotingConstants.ProtocolVersionWin7RTM) + if (serverProtocolVersion <= RemotingConstants.ProtocolVersion_2_1) { int maxEnvSize; WSManNativeApi.WSManGetSessionOptionAsDword(_wsManSessionHandle, @@ -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/remoting/server/ServerRunspacePoolDriver.cs b/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs index ba0728790e7..a5e0da4968e 100644 --- a/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs +++ b/src/System.Management.Automation/engine/remoting/server/ServerRunspacePoolDriver.cs @@ -716,7 +716,7 @@ private void HandleCreateAndInvokePowerShell(object _, RemoteDataEventArgs<Remot bool isNested = false; // The server would've dropped the protocol version of an older client was connecting - if (_serverCapability.ProtocolVersion >= RemotingConstants.ProtocolVersionWin8RTM) + if (_serverCapability.ProtocolVersion >= RemotingConstants.ProtocolVersion_2_2) { isNested = RemotingDecoder.GetIsNested(data.Data); } diff --git a/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs b/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs index d9e85e94e7a..b1e05909d2c 100644 --- a/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs +++ b/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs @@ -973,35 +973,21 @@ private bool RunServerNegotiationAlgorithm(RemoteSessionCapability clientCapabil if (onConnect) { - bool connectSupported = false; - - // Win10 server can support reconstruct/reconnect for all 2.x protocol versions - // that support reconstruct/reconnect, Protocol 2.2+ - // Major protocol version differences (2.x -> 3.x) are not supported. - // A reconstruct can only be initiated by a client that understands disconnect (2.2+), - // so we only need to check major versions from client and this server for compatibility. - if (clientProtocolVersion.Major == RemotingConstants.ProtocolVersion.Major) + // PS v7.6 server can support reconstruct/reconnect for all 2.x protocol versions that support reconstruct/reconnect (v2.2+). + // Major protocol version differences (2.x -> 3.x) are not supported. A reconstruct can only be initiated by a client that understands disconnect (v2.2+). + if (clientProtocolVersion == RemotingConstants.ProtocolVersion_2_2 || + clientProtocolVersion == RemotingConstants.ProtocolVersion_2_3) { - if (clientProtocolVersion.Minor == RemotingConstants.ProtocolVersionWin8RTM.Minor) - { - // Report that server is Win8 version to the client - // Protocol: 2.2 - connectSupported = true; - serverProtocolVersion = RemotingConstants.ProtocolVersionWin8RTM; - Context.ServerCapability.ProtocolVersion = serverProtocolVersion; - } - else if (clientProtocolVersion.Minor > RemotingConstants.ProtocolVersionWin8RTM.Minor) - { - // All other minor versions are supported and the server returns its full capability - // Protocol: 2.3, 2.4, 2.5 ... - connectSupported = true; - } + // Report the server as the same version to the client. + // Client protocol: v2.2, v2.3 + serverProtocolVersion = clientProtocolVersion; + Context.ServerCapability.ProtocolVersion = serverProtocolVersion; } - - if (!connectSupported) + else if (!(clientProtocolVersion.Major == serverProtocolVersion.Major && + clientProtocolVersion.Minor >= serverProtocolVersion.Minor)) { // Throw for protocol versions 2.x that don't support disconnect/reconnect. - // Protocol: < 2.2 + // Client protocol: < 2.2 PSRemotingDataStructureException reasonOfFailure = new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerConnectFailedOnNegotiation, RemoteDataNameStrings.PS_STARTUP_PROTOCOL_VERSION_NAME, @@ -1010,47 +996,23 @@ private bool RunServerNegotiationAlgorithm(RemoteSessionCapability clientCapabil RemotingConstants.ProtocolVersion); throw reasonOfFailure; } + + // All other minor versions are supported and the server returns its full capability. + // Client protocol: v2.4, v2.5 ... } else { - // Win10 server can support Win8 client - if (clientProtocolVersion == RemotingConstants.ProtocolVersionWin8RTM && - ( - (serverProtocolVersion == RemotingConstants.ProtocolVersionWin10RTM) - )) - { - // - report that server is Win8 version to the client - serverProtocolVersion = RemotingConstants.ProtocolVersionWin8RTM; - Context.ServerCapability.ProtocolVersion = serverProtocolVersion; - } - - // Win8, Win10 server can support Win7 client - if (clientProtocolVersion == RemotingConstants.ProtocolVersionWin7RTM && - ( - (serverProtocolVersion == RemotingConstants.ProtocolVersionWin8RTM) || - (serverProtocolVersion == RemotingConstants.ProtocolVersionWin10RTM) - )) - { - // - report that server is Win7 version to the client - serverProtocolVersion = RemotingConstants.ProtocolVersionWin7RTM; - Context.ServerCapability.ProtocolVersion = serverProtocolVersion; - } - - // Win7, Win8, Win10 server can support Win7 RC client - if (clientProtocolVersion == RemotingConstants.ProtocolVersionWin7RC && - ( - (serverProtocolVersion == RemotingConstants.ProtocolVersionWin7RTM) || - (serverProtocolVersion == RemotingConstants.ProtocolVersionWin8RTM) || - (serverProtocolVersion == RemotingConstants.ProtocolVersionWin10RTM) - )) + if (clientProtocolVersion == RemotingConstants.ProtocolVersion_2_0 || + clientProtocolVersion == RemotingConstants.ProtocolVersion_2_1 || + clientProtocolVersion == RemotingConstants.ProtocolVersion_2_2 || + clientProtocolVersion == RemotingConstants.ProtocolVersion_2_3) { - // - report that server is RC version to the client - serverProtocolVersion = RemotingConstants.ProtocolVersionWin7RC; + // We support the those client versions and report the server as the same version to the client. + serverProtocolVersion = clientProtocolVersion; Context.ServerCapability.ProtocolVersion = serverProtocolVersion; } - - if (!((clientProtocolVersion.Major == serverProtocolVersion.Major) && - (clientProtocolVersion.Minor >= serverProtocolVersion.Minor))) + else if (!(clientProtocolVersion.Major == serverProtocolVersion.Major && + clientProtocolVersion.Minor >= serverProtocolVersion.Minor)) { PSRemotingDataStructureException reasonOfFailure = new PSRemotingDataStructureException(RemotingErrorIdStrings.ServerNegotiationFailed, diff --git a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs index f4020547432..027c4886380 100644 --- a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs +++ b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Buffers; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; @@ -5297,7 +5298,8 @@ public override DynamicMetaObject FallbackGetMember(DynamicMetaObject target, Dy var propertyAccessor = adapterData.member as PropertyInfo; if (propertyAccessor != null) { - if (propertyAccessor.GetMethod.IsFamily && + var propertyGetter = propertyAccessor.GetMethod; + if ((propertyGetter.IsFamily || propertyGetter.IsFamilyOrAssembly) && (_classScope == null || !_classScope.IsSubclassOf(propertyAccessor.DeclaringType))) { return GenerateGetPropertyException(restrictions).WriteToDebugLog(this); @@ -5757,8 +5759,8 @@ internal PSMemberInfo GetPSMemberInfo(DynamicMetaObject target, var getMethod = propertyInfo.GetGetMethod(nonPublic: true); var setMethod = propertyInfo.GetSetMethod(nonPublic: true); - if ((getMethod == null || getMethod.IsFamily || getMethod.IsPublic) && - (setMethod == null || setMethod.IsFamily || setMethod.IsPublic)) + if ((getMethod == null || getMethod.IsPublic || getMethod.IsFamily || getMethod.IsFamilyOrAssembly) && + (setMethod == null || setMethod.IsPublic || setMethod.IsFamily || setMethod.IsFamilyOrAssembly)) { memberInfo = new PSProperty(this.Name, PSObject.DotNetInstanceAdapter, target.Value, new DotNetAdapter.PropertyCacheEntry(propertyInfo)); } @@ -5768,7 +5770,7 @@ internal PSMemberInfo GetPSMemberInfo(DynamicMetaObject target, var fieldInfo = member as FieldInfo; if (fieldInfo != null) { - if (fieldInfo.IsFamily) + if (fieldInfo.IsFamily || fieldInfo.IsFamilyOrAssembly) { memberInfo = new PSProperty(this.Name, PSObject.DotNetInstanceAdapter, target.Value, new DotNetAdapter.PropertyCacheEntry(fieldInfo)); } @@ -5776,7 +5778,7 @@ internal PSMemberInfo GetPSMemberInfo(DynamicMetaObject target, else { var methodInfo = member as MethodInfo; - if (methodInfo != null && (methodInfo.IsPublic || methodInfo.IsFamily)) + if (methodInfo != null && (methodInfo.IsPublic || methodInfo.IsFamily || methodInfo.IsFamilyOrAssembly)) { candidateMethods ??= new List<MethodBase>(); @@ -6291,7 +6293,8 @@ public override DynamicMetaObject FallbackSetMember(DynamicMetaObject target, Dy var targetExpr = _static ? null : PSGetMemberBinder.GetTargetExpr(target, data.member.DeclaringType); if (propertyInfo != null) { - if (propertyInfo.SetMethod.IsFamily && + var propertySetter = propertyInfo.SetMethod; + if ((propertySetter.IsFamily || propertySetter.IsFamilyOrAssembly) && (_classScope == null || !_classScope.IsSubclassOf(propertyInfo.DeclaringType))) { return GeneratePropertyAssignmentException(restrictions).WriteToDebugLog(this); @@ -6533,6 +6536,21 @@ public override DynamicMetaObject FallbackInvoke(DynamicMetaObject target, Dynam internal sealed class PSInvokeMemberBinder : InvokeMemberBinder { + [TraceSource("MethodInvocation", "Traces the invocation of .NET methods.")] + internal static readonly PSTraceSource MethodInvocationTracer = + PSTraceSource.GetTracer( + "MethodInvocation", + "Traces the invocation of .NET methods.", + false); + + private static readonly SearchValues<string> s_whereSearchValues = SearchValues.Create( + ["Where", "PSWhere"], + StringComparison.OrdinalIgnoreCase); + + private static readonly SearchValues<string> s_foreachSearchValues = SearchValues.Create( + ["ForEach", "PSForEach"], + StringComparison.OrdinalIgnoreCase); + internal enum MethodInvocationType { Ordinary, @@ -6681,12 +6699,14 @@ public override DynamicMetaObject FallbackInvokeMember(DynamicMetaObject target, .WriteToDebugLog(this); BindingRestrictions argRestrictions = args.Aggregate(BindingRestrictions.Empty, static (current, arg) => current.Merge(arg.PSGetMethodArgumentRestriction())); - if (string.Equals(Name, "Where", StringComparison.OrdinalIgnoreCase)) + // We need to pass the empty enumerator to the ForEach/Where operators, so that they can return an empty collection. + // The ForEach/Where operators will not be able to call the script block if the enumerator is empty. + if (s_whereSearchValues.Contains(Name)) { return InvokeWhereOnCollection(emptyEnumerator, args, argRestrictions).WriteToDebugLog(this); } - if (string.Equals(Name, "ForEach", StringComparison.OrdinalIgnoreCase)) + if (s_foreachSearchValues.Contains(Name)) { return InvokeForEachOnCollection(emptyEnumerator, args, argRestrictions).WriteToDebugLog(this); } @@ -6866,12 +6886,12 @@ public override DynamicMetaObject FallbackInvokeMember(DynamicMetaObject target, if (!_static && !_nonEnumerating && target.Value != AutomationNull.Value) { // Invoking Where and ForEach operators on collections. - if (string.Equals(Name, "Where", StringComparison.OrdinalIgnoreCase)) + if (s_whereSearchValues.Contains(Name)) { return InvokeWhereOnCollection(target, args, restrictions).WriteToDebugLog(this); } - if (string.Equals(Name, "ForEach", StringComparison.OrdinalIgnoreCase)) + if (s_foreachSearchValues.Contains(Name)) { return InvokeForEachOnCollection(target, args, restrictions).WriteToDebugLog(this); } @@ -6943,6 +6963,17 @@ internal static DynamicMetaObject InvokeDotNetMethod( expr = Expression.Block(expr, ExpressionCache.AutomationNullConstant); } + if (MethodInvocationTracer.IsEnabled) + { + expr = Expression.Block( + Expression.Call( + Expression.Constant(MethodInvocationTracer), + CachedReflectionInfo.PSTraceSource_WriteLine, + Expression.Constant("Invoking method: {0}"), + Expression.Constant(result.methodDefinition)), + expr); + } + // If we're calling SteppablePipeline.{Begin|Process|End}, we don't want // to wrap exceptions - this is very much a special case to help error // propagation and ensure errors are attributed to the correct code (the @@ -7170,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) @@ -7490,7 +7528,7 @@ internal static object InvokeAdaptedMember(object obj, string methodName, object // As a last resort, we invoke 'Where' and 'ForEach' operators on singletons like // ([pscustomobject]@{ foo = 'bar' }).Foreach({$_}) // ([pscustomobject]@{ foo = 'bar' }).Where({1}) - if (string.Equals(methodName, "Where", StringComparison.OrdinalIgnoreCase)) + if (s_whereSearchValues.Contains(methodName)) { var enumerator = (new object[] { obj }).GetEnumerator(); switch (args.Length) @@ -7506,7 +7544,7 @@ internal static object InvokeAdaptedMember(object obj, string methodName, object } } - if (string.Equals(methodName, "Foreach", StringComparison.OrdinalIgnoreCase)) + if (s_foreachSearchValues.Contains(methodName)) { var enumerator = (new object[] { obj }).GetEnumerator(); object[] argsToPass; @@ -7891,7 +7929,7 @@ public override DynamicMetaObject FallbackInvokeMember(DynamicMetaObject target, ? BindingRestrictions.GetTypeRestriction(target.Expression, target.Value.GetType()) : target.PSGetTypeRestriction(); restrictions = args.Aggregate(restrictions, static (current, arg) => current.Merge(arg.PSGetMethodArgumentRestriction())); - var newConstructors = DotNetAdapter.GetMethodInformationArray(ctors.Where(static c => c.IsPublic || c.IsFamily).ToArray()); + var newConstructors = DotNetAdapter.GetMethodInformationArray(ctors.Where(static c => c.IsPublic || c.IsFamily || c.IsFamilyOrAssembly).ToArray()); return PSInvokeMemberBinder.InvokeDotNetMethod(_callInfo, "new", _constraints, PSInvokeMemberBinder.MethodInvocationType.BaseCtor, target, args, restrictions, newConstructors, typeof(MethodException)); } 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 3423709d3cf..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); } @@ -2271,8 +2296,7 @@ protected override void NewItem( // for hardlinks we resolve the target to an absolute path if (!IsAbsolutePath(strTargetPath)) { - // there is already a check before here so that strTargetPath should only resolve to 1 path - strTargetPath = SessionState.Path.GetResolvedPSPathFromPSPath(strTargetPath).FirstOrDefault()?.Path; + strTargetPath = SessionState.Path.GetUnresolvedProviderPathFromPSPath(strTargetPath); } exists = GetFileSystemInfo(strTargetPath, out isDirectory) != null; @@ -2666,12 +2690,6 @@ private void CreateDirectory(string path, bool streamOutput) !string.IsNullOrEmpty(path), "The caller should verify path"); - // Get the parent path - string parentPath = GetParentPath(path, null); - - // The directory name - string childName = GetChildName(path); - ErrorRecord error = null; if (!Force && ItemExists(path, out error)) { @@ -2701,7 +2719,7 @@ private void CreateDirectory(string path, bool streamOutput) if (ShouldProcess(resource, action)) { - var result = Directory.CreateDirectory(Path.Combine(parentPath, childName)); + var result = Directory.CreateDirectory(path); if (streamOutput) { @@ -8292,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/CommandBaseStrings.resx b/src/System.Management.Automation/resources/CommandBaseStrings.resx index 0ff1aac4716..e869e98e742 100644 --- a/src/System.Management.Automation/resources/CommandBaseStrings.resx +++ b/src/System.Management.Automation/resources/CommandBaseStrings.resx @@ -163,7 +163,7 @@ however that is used for ApplicationFailedExceptions thrown when the NativeCommandProcessor fails in an unexpected way. In this case, we have a more specific error for the native command scenario, so the two are not conflated. --> - <value>Program "{0}" ended with non-zero exit code: {1}.</value> + <value>Program "{0}" ended with non-zero exit code: {1} ({2}).</value> </data> <data name="ShouldProcessMessage" xml:space="preserve"> <value>Performing the operation "{0}" on target "{1}".</value> 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 c201facc3e2..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] @@ -586,4 +586,25 @@ using type <AliasName> = <.NET-type></value> <data name="RegistryUnknownToolTip" xml:space="preserve"> <value>An unsupported registry data type.</value> </data> + <data name="SeparatorCommaToolTip" xml:space="preserve"> + <value>',' - Comma</value> + </data> + <data name="SeparatorCommaSpaceToolTip" xml:space="preserve"> + <value>', ' - Comma-Space</value> + </data> + <data name="SeparatorSemiColonToolTip" xml:space="preserve"> + <value>';' - Semi-Colon</value> + </data> + <data name="SeparatorSemiColonSpaceToolTip" xml:space="preserve"> + <value>'; ' - Semi-Colon-Space</value> + </data> + <data name="SeparatorNewlineToolTip" xml:space="preserve"> + <value>{0} - Newline</value> + </data> + <data name="SeparatorDashToolTip" xml:space="preserve"> + <value>'-' - Dash</value> + </data> + <data name="SeparatorSpaceToolTip" xml:space="preserve"> + <value>' ' - Space</value> + </data> </root> 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 2f7c991abc4..5ff5881bab4 100644 --- a/src/System.Management.Automation/security/SecureStringHelper.cs +++ b/src/System.Management.Automation/security/SecureStringHelper.cs @@ -31,7 +31,7 @@ internal static class SecureStringHelper /// </summary> /// <param name="data">Input data.</param> /// <returns>A SecureString .</returns> - private static SecureString New(byte[] data) + internal static SecureString New(byte[] data) { if ((data.Length % 2) != 0) { @@ -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 2441d416a4d..c82c96ecdc3 100644 --- a/src/System.Management.Automation/utils/CryptoUtils.cs +++ b/src/System.Management.Automation/utils/CryptoUtils.cs @@ -5,7 +5,6 @@ using System.IO; using System.Linq; using System.Management.Automation.Remoting; -using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Security; using System.Security.Cryptography; @@ -434,6 +433,7 @@ internal string SafeExportSessionKey() GenerateSessionKey(); // encrypt it + // codeql[cs/cryptography/rsa-unapproved-encryption-padding-scheme] - PowerShell v7.4 and later versions have deprecated the key exchange in the remoting protocol. This code is kept only for backward compatibility reason. byte[] encryptedKey = _rsa.Encrypt(_aes.Key, RSAEncryptionPadding.Pkcs1); // convert the key to capi simpleblob format before exporting @@ -467,6 +467,7 @@ internal void ImportSessionKeyFromBase64EncodedString(string sessionKey) byte[] sessionKeyBlob = Convert.FromBase64String(sessionKey); byte[] rsaEncryptedKey = PSCryptoNativeConverter.FromCapiSimpleKeyBlob(sessionKeyBlob); + // codeql[cs/cryptography/rsa-unapproved-encryption-padding-scheme] - PowerShell v7.4 and later versions have deprecated the key exchange in the remoting protocol. This code is kept only for backward compatibility reason. _aes.Key = _rsa.Decrypt(rsaEncryptedKey, RSAEncryptionPadding.Pkcs1); // now we have imported the key and will be able to @@ -575,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 @@ -658,6 +650,78 @@ protected void RunKeyExchangeIfRequired() } } + /// <summary> + /// Gets the bytes of a secure string. + /// </summary> + private static byte[] GetBytesFromSecureString(SecureString secureString) + { + return secureString is null + ? null + : Microsoft.PowerShell.SecureStringHelper.GetData(secureString); + } + + /// <summary> + /// Gets a secure string from the specified byte array. + /// </summary> + private static SecureString GetSecureStringFromBytes(byte[] data) + { + Dbg.Assert(data is not null, "The passed-in data cannot be null."); + + try + { + return Microsoft.PowerShell.SecureStringHelper.New(data); + } + finally + { + // zero out the contents + Array.Clear(data); + } + } + + /// <summary> + /// Convert a secure string to a base64 encoded string. + /// </summary> + protected string ConvertSecureStringToBase64String(SecureString secureString) + { + string dataAsString = null; + byte[] data = GetBytesFromSecureString(secureString); + + if (data is not null) + { + try + { + dataAsString = Convert.ToBase64String(data); + } + finally + { + Array.Clear(data); + } + } + + return dataAsString; + } + + /// <summary> + /// Convert a base64 encoded string to a secure string. + /// </summary> + /// <param name="base64String"></param> + /// <returns></returns> + protected SecureString ConvertBase64StringToSecureString(string base64String) + { + try + { + byte[] data = Convert.FromBase64String(base64String); + return GetSecureStringFromBytes(data); + } + catch (FormatException) + { + // do nothing + // this catch is to ensure that the exception doesn't + // go unhandled leading to a crash + throw new PSCryptoException(); + } + } + /// <summary> /// Core logic to encrypt a string. Assumes session key is already generated. /// </summary> @@ -671,18 +735,10 @@ protected string EncryptSecureStringCore(SecureString secureString) if (_rsaCryptoProvider.CanEncrypt) { - IntPtr ptr = Marshal.SecureStringToCoTaskMemUnicode(secureString); + byte[] data = GetBytesFromSecureString(secureString); - if (ptr != IntPtr.Zero) + if (data is not null) { - byte[] data = new byte[secureString.Length * 2]; - for (int i = 0; i < data.Length; i++) - { - data[i] = Marshal.ReadByte(ptr, i); - } - - Marshal.ZeroFreeCoTaskMemUnicode(ptr); - try { byte[] encryptedData = _rsaCryptoProvider.EncryptWithSessionKey(data); @@ -690,10 +746,7 @@ protected string EncryptSecureStringCore(SecureString secureString) } finally { - for (int j = 0; j < data.Length; j++) - { - data[j] = 0; - } + Array.Clear(data); } } } @@ -723,10 +776,11 @@ protected SecureString DecryptSecureStringCore(string encryptedString) // happened successfully if (_rsaCryptoProvider.CanEncrypt) { - byte[] data = null; try { - data = Convert.FromBase64String(encryptedString); + byte[] data = Convert.FromBase64String(encryptedString); + byte[] decryptedData = _rsaCryptoProvider.DecryptWithSessionKey(data); + secureString = GetSecureStringFromBytes(decryptedData); } catch (FormatException) { @@ -735,36 +789,6 @@ protected SecureString DecryptSecureStringCore(string encryptedString) // go unhandled leading to a crash throw new PSCryptoException(); } - - if (data != null) - { - byte[] decryptedData = _rsaCryptoProvider.DecryptWithSessionKey(data); - - secureString = new SecureString(); - UInt16 value = 0; - try - { - for (int i = 0; i < decryptedData.Length; i += 2) - { - value = (UInt16)(decryptedData[i] + (UInt16)(decryptedData[i + 1] << 8)); - secureString.AppendChar((char)value); - value = 0; - } - } - finally - { - // if there was an exception for whatever reason, - // clear the last value store in Value - value = 0; - - // zero out the contents - for (int i = 0; i < decryptedData.Length; i += 2) - { - decryptedData[i] = 0; - decryptedData[i + 1] = 0; - } - } - } } else { @@ -867,14 +891,29 @@ internal PSRemotingCryptoHelperServer() internal override string EncryptSecureString(SecureString secureString) { // session!=null check required for DRTs TestEncryptSecureString* entries in CryptoUtilsTest/UTUtils.dll - // for newer clients, server will never initiate key exchange. - // for server, just the session key is required to encrypt/decrypt anything - if (Session is ServerRemoteSession session && session.Context.ClientCapability.ProtocolVersion >= RemotingConstants.ProtocolVersionWin8RTM) + bool initiateKeyExchange = true; + + if (Session is ServerRemoteSession session) { - _rsaCryptoProvider.GenerateSessionKey(); + Version clientProtocolVersion = session.Context.ClientCapability.ProtocolVersion; + if (clientProtocolVersion >= RemotingConstants.ProtocolVersion_2_4) + { + // For client v2.4+, we no longer encrypt secure strings, but rely on the underlying secure transport to do the right thing. + return ConvertSecureStringToBase64String(secureString); + } + + if (clientProtocolVersion >= RemotingConstants.ProtocolVersion_2_2) + { + // For client v2.2+, server will never initiate key exchange. + // For server, just the session key is required to encrypt/decrypt anything + initiateKeyExchange = false; + _rsaCryptoProvider.GenerateSessionKey(); + } } - else // older clients + + if (initiateKeyExchange) { + // older clients. RunKeyExchangeIfRequired(); } @@ -883,6 +922,12 @@ internal override string EncryptSecureString(SecureString secureString) internal override SecureString DecryptSecureString(string encryptedString) { + if (Session is ServerRemoteSession session && session.Context.ClientCapability.ProtocolVersion >= RemotingConstants.ProtocolVersion_2_4) + { + // For client v2.4+, we no longer encrypt secure strings, but rely on the underlying secure transport to do the right thing. + return ConvertBase64StringToSecureString(encryptedString); + } + RunKeyExchangeIfRequired(); return DecryptSecureStringCore(encryptedString); @@ -994,6 +1039,12 @@ internal PSRemotingCryptoHelperClient() internal override string EncryptSecureString(SecureString secureString) { + if (Session is ClientRemoteSession session && session.ServerProtocolVersion >= RemotingConstants.ProtocolVersion_2_4) + { + // For server v2.4+, we no longer encrypt secure strings, but rely on the underlying secure transport to do the right thing. + return ConvertSecureStringToBase64String(secureString); + } + RunKeyExchangeIfRequired(); return EncryptSecureStringCore(secureString); @@ -1001,6 +1052,12 @@ internal override string EncryptSecureString(SecureString secureString) internal override SecureString DecryptSecureString(string encryptedString) { + if (Session is ClientRemoteSession session && session.ServerProtocolVersion >= RemotingConstants.ProtocolVersion_2_4) + { + // For server v2.4+, we no longer encrypt secure strings, but rely on the underlying secure transport to do the right thing. + return ConvertBase64StringToSecureString(encryptedString); + } + RunKeyExchangeIfRequired(); return DecryptSecureStringCore(encryptedString); 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 73c55497c5b..dddbb915eca 100644 --- a/src/powershell-win-core/powershell-win-core.csproj +++ b/src/powershell-win-core/powershell-win-core.csproj @@ -13,7 +13,6 @@ <ApplicationManifest>..\..\assets\pwsh.manifest</ApplicationManifest> <TargetPlatformIdentifier>Windows</TargetPlatformIdentifier> <TargetPlatformVersion>8.0</TargetPlatformVersion> - <EnableUnsafeBinaryFormatterSerialization>true</EnableUnsafeBinaryFormatterSerialization> </PropertyGroup> <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" /> @@ -30,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/hosting/hosting.tests.csproj b/test/hosting/hosting.tests.csproj index cb72e0a90ef..1c40094c7ff 100644 --- a/test/hosting/hosting.tests.csproj +++ b/test/hosting/hosting.tests.csproj @@ -21,6 +21,12 @@ <PackageReference Include="Xunit.SkippableFact" Version="1.3.6" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" /> + <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" /> </ItemGroup> </Project> 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 1852caa69c2..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.14.0" /> - <PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.14.0" /> + <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 48cad7ffd8a..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.14.0" /> - <PackageReference Include="Perfolizer" Version="0.4.0" /> + <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 c7d6ee744e0..f8762a63929 100644 --- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 @@ -36,13 +36,13 @@ Describe "TabCompletion" -Tags CI { It 'Should not include duplicate command results' { $OldModulePath = $env:PSModulePath $tempDir = Join-Path -Path $TestDrive -ChildPath "TempPsModuleDir" + $ModuleDirs = @( + Join-Path $tempDir "TestModule1\1.0" + Join-Path $tempDir "TestModule1\1.1" + Join-Path $tempDir "TestModule2\1.0" + ) try { - $ModuleDirs = @( - Join-Path $tempDir "TestModule1\1.0" - Join-Path $tempDir "TestModule1\1.1" - Join-Path $tempDir "TestModule2\1.0" - ) foreach ($Dir in $ModuleDirs) { $NewDir = New-Item -Path $Dir -ItemType Directory -Force @@ -61,6 +61,36 @@ Describe "TabCompletion" -Tags CI { finally { $env:PSModulePath = $OldModulePath + Remove-Item -LiteralPath $ModuleDirs -Recurse -Force + } + } + + It 'Should not include duplicate module results' { + $OldModulePath = $env:PSModulePath + $tempDir = Join-Path -Path $TestDrive -ChildPath "TempPsModuleDir" + try + { + $ModuleDirs = @( + Join-Path $tempDir "TestModule1\1.0" + Join-Path $tempDir "TestModule1\1.1" + ) + foreach ($Dir in $ModuleDirs) + { + $NewDir = New-Item -Path $Dir -ItemType Directory -Force + $ModuleName = $NewDir.Parent.Name + Set-Content -Value 'MyTestFunction{}' -LiteralPath "$($NewDir.FullName)\$ModuleName.psm1" + New-ModuleManifest -Path "$($NewDir.FullName)\$ModuleName.psd1" -RootModule "$ModuleName.psm1" -FunctionsToExport "MyTestFunction" -ModuleVersion $NewDir.Name + } + + $env:PSModulePath += [System.IO.Path]::PathSeparator + $tempDir + $Res = TabExpansion2 -inputScript 'Import-Module -Name TestModule' + $Res.CompletionMatches.Count | Should -Be 1 + $Res.CompletionMatches[0].CompletionText | Should -Be TestModule1 + } + finally + { + $env:PSModulePath = $OldModulePath + Remove-Item -LiteralPath $ModuleDirs -Recurse -Force } } @@ -150,6 +180,46 @@ Describe "TabCompletion" -Tags CI { $res.CompletionMatches[0].ToolTip | Should -BeExactly $Expected } + It 'Should complete environment variable' { + try { + $env:PWSH_TEST_1 = 'value 1' + $env:PWSH_TEST_2 = 'value 2' + + $res = TabExpansion2 -inputScript '$env:PWSH_TEST_' + $res.CompletionMatches.Count | Should -Be 2 + $res.CompletionMatches[0].CompletionText | Should -BeExactly '$env:PWSH_TEST_1' + $res.CompletionMatches[0].ListItemText | Should -BeExactly 'PWSH_TEST_1' + $res.CompletionMatches[0].ToolTip | Should -BeExactly 'PWSH_TEST_1' + $res.CompletionMatches[1].CompletionText | Should -BeExactly '$env:PWSH_TEST_2' + $res.CompletionMatches[1].ListItemText | Should -BeExactly 'PWSH_TEST_2' + $res.CompletionMatches[1].ToolTip | Should -BeExactly 'PWSH_TEST_2' + } + finally { + $env:PWSH_TEST_1 = $null + $env:PWSH_TEST_2 = $null + } + } + + It 'Should complete function variable' { + try { + Function Test-PwshTest1 {} + Function Test-PwshTest2 {} + + $res = TabExpansion2 -inputScript '${function:Test-PwshTest' + $res.CompletionMatches.Count | Should -Be 2 + $res.CompletionMatches[0].CompletionText | Should -BeExactly '${function:Test-PwshTest1}' + $res.CompletionMatches[0].ListItemText | Should -BeExactly 'Test-PwshTest1' + $res.CompletionMatches[0].ToolTip | Should -BeExactly 'Test-PwshTest1' + $res.CompletionMatches[1].CompletionText | Should -BeExactly '${function:Test-PwshTest2}' + $res.CompletionMatches[1].ListItemText | Should -BeExactly 'Test-PwshTest2' + $res.CompletionMatches[1].ToolTip | Should -BeExactly 'Test-PwshTest2' + } + finally { + Remove-Item function:Test-PwshTest1 -ErrorAction SilentlyContinue + Remove-Item function:Test-PwshTest1 -ErrorAction SilentlyContinue + } + } + It 'Should complete scoped variable with description and value <Value>' -TestCases @( @{ Value = 1; Expected = '[int]$VariableWithDescription - Variable description' } @{ Value = 'string'; Expected = '[string]$VariableWithDescription - Variable description' } @@ -1044,6 +1114,174 @@ param([ValidatePattern( $res.CompletionMatches[0].CompletionText | Should -BeExactly '$TestVar1' } + It 'Should complete variable assigned in ParenExpression' { + $res = TabExpansion2 -inputScript '($ParenVar) = 1; $ParenVa' + $res.CompletionMatches[0].CompletionText | Should -BeExactly '$ParenVar' + } + + It 'Should complete variable assigned in ArrayLiteral' { + $res = TabExpansion2 -inputScript '$DemoVar1, $DemoVar2 = 1..10; $DemoVar' + $res.CompletionMatches[0].CompletionText | Should -BeExactly '$DemoVar1' + $res.CompletionMatches[1].CompletionText | Should -BeExactly '$DemoVar2' + } + + It 'Should include parameter help message in tool tip - SingleMatch <SingleMatch>' -TestCases @( + @{ SingleMatch = $true } + @{ SingleMatch = $false } + ) { + param ($SingleMatch) + + Function Test-Function { + param ( + [Parameter(HelpMessage = 'Some help message')] + $ParamWithHelp, + + $ParamWithoutHelp + ) + } + + $expected = '[Object] ParamWithHelp - Some help message' + + if ($SingleMatch) { + $Script = 'Test-Function -ParamWithHelp' + $res = (TabExpansion2 -inputScript $Script).CompletionMatches + } + else { + $Script = 'Test-Function -' + $res = (TabExpansion2 -inputScript $Script).CompletionMatches | Where-Object CompletionText -eq '-ParamWithHelp' + } + + $res.Count | Should -Be 1 + $res.CompletionText | Should -BeExactly '-ParamWithHelp' + $res.ToolTip | Should -BeExactly $expected + } + + It 'Should include parameter help resource message in tool tip - SingleMatch <SingleMatch>' -TestCases @( + @{ SingleMatch = $true } + @{ SingleMatch = $false } + ) { + param ($SingleMatch) + + $expected = '`[string`] Activity - *' + + if ($SingleMatch) { + $Script = 'Write-Progress -Activity' + $res = (TabExpansion2 -inputScript $Script).CompletionMatches + } + else { + $Script = 'Write-Progress -' + $res = (TabExpansion2 -inputScript $Script).CompletionMatches | Where-Object CompletionText -eq '-Activity' + } + + $res.Count | Should -Be 1 + $res.CompletionText | Should -BeExactly '-Activity' + $res.ToolTip | Should -BeLikeExactly $expected + } + + It 'Should skip empty parameter HelpMessage with multiple parameters - SingleMatch <SingleMatch>' -TestCases @( + @{ SingleMatch = $true } + @{ SingleMatch = $false } + ) { + param ($SingleMatch) + + Function Test-Function { + [CmdletBinding(DefaultParameterSetName = 'SetWithoutHelp')] + param ( + [Parameter(ParameterSetName = 'SetWithHelp', HelpMessage = 'Help Message')] + [Parameter(ParameterSetName = 'SetWithoutHelp')] + [string] + $ParamWithHelp, + + [Parameter(ParameterSetName = 'SetWithHelp')] + [switch] + $ParamWithoutHelp + ) + } + + $expected = '[string] ParamWithHelp - Help Message' + + if ($SingleMatch) { + $Script = 'Test-Function -ParamWithHelp' + $res = (TabExpansion2 -inputScript $Script).CompletionMatches + } + else { + $Script = 'Test-Function -' + $res = (TabExpansion2 -inputScript $Script).CompletionMatches | Where-Object CompletionText -eq '-ParamWithHelp' + } + + $res.Count | Should -Be 1 + $res.CompletionText | Should -BeExactly '-ParamWithHelp' + $res.ToolTip | Should -BeExactly $expected + } + + It 'Should retrieve help message from dynamic parameter' { + Function Test-Function { + [CmdletBinding()] + param () + dynamicparam { + $attr = [System.Management.Automation.ParameterAttribute]@{ + HelpMessage = "Howdy partner" + } + $attrCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new() + $attrCollection.Add($attr) + + $dynParam = [System.Management.Automation.RuntimeDefinedParameter]::new('DynamicParam', [string], $attrCollection) + + $paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() + $paramDictionary.Add('DynamicParam', $dynParam) + $paramDictionary + } + + end {} + } + + $expected = '[string] DynamicParam - Howdy partner' + $Script = 'Test-Function -' + $res = (TabExpansion2 -inputScript $Script).CompletionMatches | Where-Object CompletionText -eq '-DynamicParam' + $res.Count | Should -Be 1 + $res.CompletionText | Should -BeExactly '-DynamicParam' + $res.ToolTip | Should -BeExactly $expected + } + + It 'Should have type and name for parameter without help message' { + Function Test-Function { + param ( + [Parameter()] + $WithParamAttribute, + + $WithoutParamAttribute + ) + } + + $Script = 'Test-Function -' + $res = (TabExpansion2 -inputScript $Script).CompletionMatches | + Where-Object CompletionText -in '-WithParamAttribute', '-WithoutParamAttribute' | + Sort-Object CompletionText + $res.Count | Should -Be 2 + + $res.CompletionText[0] | Should -BeExactly '-WithoutParamAttribute' + $res.ToolTip[0] | Should -BeExactly '[Object] WithoutParamAttribute' + + $res.CompletionText[1] | Should -BeExactly '-WithParamAttribute' + $res.ToolTip[1] | Should -BeExactly '[Object] WithParamAttribute' + } + + It 'Should ignore errors when faling to get HelpMessage resource' { + Function Test-Function { + param ( + [Parameter(HelpMessageBaseName="invalid", HelpMessageResourceId="SomeId")] + $InvalidHelpParam + ) + } + + $expected = '[Object] InvalidHelpParam' + $Script = 'Test-Function -InvalidHelpParam' + $res = (TabExpansion2 -inputScript $Script).CompletionMatches + $res.Count | Should -Be 1 + $res.CompletionText | Should -BeExactly '-InvalidHelpParam' + $res.ToolTip | Should -BeExactly $expected + } + Context 'Start-Process -Verb parameter completion' { BeforeAll { function GetProcessInfoVerbs([string]$path, [switch]$singleQuote, [switch]$doubleQuote) { @@ -1328,6 +1566,13 @@ param([ValidatePattern( $res = TabExpansion2 -inputScript $TextInput -cursorColumn $TextInput.Length $completionText = $res.CompletionMatches.CompletionText $completionText -join ' ' | Should -BeExactly $ExpectedPropertyTypes + + foreach ($match in $res.CompletionMatches) { + $completionText = $match.CompletionText.Replace("""", "").Replace("'", "") + $listItemText = $match.ListItemText + $completionText | Should -BeExactly $listItemText + $match.ToolTip | Should -Not -BeNullOrEmpty + } } It "Test fallback to provider of current location if no path specified" -Skip:(!$IsWindows) { @@ -1471,6 +1716,112 @@ param([ValidatePattern( } } + Context "Join-String -Separator & -FormatString parameter completion" { + BeforeAll { + if ($IsWindows) { + $allSeparators = "',' ', ' ';' '; ' ""``r``n"" '-' ' '" + $allFormatStrings = "'[{0}]' '{0:N2}' ""``r``n ```${0}"" ""``r``n [string] ```${0}""" + $newlineSeparator = """``r``n""" + $newlineFormatStrings = """``r``n ```${0}"" ""``r``n [string] ```${0}""" + } + else { + $allSeparators = "',' ', ' ';' '; ' ""``n"" '-' ' '" + $allFormatStrings = "'[{0}]' '{0:N2}' ""``n ```${0}"" ""``n [string] ```${0}""" + $newlineSeparator = """``n""" + $newlineFormatStrings = """``n ```${0}"" ""``n [string] ```${0}""" + } + + $commaSeparators = "',' ', '" + $semiColonSeparators = "';' '; '" + + $squareBracketFormatString = "'[{0}]'" + $curlyBraceFormatString = "'{0:N2}'" + } + + It "Should complete for '<TextInput>'" -TestCases @( + @{ TextInput = "Join-String -Separator "; Expected = $allSeparators } + @{ TextInput = "Join-String -Separator '"; Expected = $allSeparators } + @{ TextInput = "Join-String -Separator """; Expected = $allSeparators.Replace("'", """") } + @{ TextInput = "Join-String -Separator ',"; Expected = $commaSeparators } + @{ TextInput = "Join-String -Separator "","; Expected = $commaSeparators.Replace("'", """") } + @{ TextInput = "Join-String -Separator ';"; Expected = $semiColonSeparators } + @{ TextInput = "Join-String -Separator "";"; Expected = $semiColonSeparators.Replace("'", """") } + @{ TextInput = "Join-String -FormatString "; Expected = $allFormatStrings } + @{ TextInput = "Join-String -FormatString '"; Expected = $allFormatStrings } + @{ TextInput = "Join-String -FormatString """; Expected = $allFormatStrings.Replace("'", """") } + @{ TextInput = "Join-String -FormatString ["; Expected = $squareBracketFormatString } + @{ TextInput = "Join-String -FormatString '["; Expected = $squareBracketFormatString } + @{ TextInput = "Join-String -FormatString ""["; Expected = $squareBracketFormatString.Replace("'", """") } + @{ TextInput = "Join-String -FormatString '{"; Expected = $curlyBraceFormatString } + @{ TextInput = "Join-String -FormatString ""{"; Expected = $curlyBraceFormatString.Replace("'", """") } + ) { + param($TextInput, $Expected) + $res = TabExpansion2 -inputScript $TextInput -cursorColumn $TextInput.Length + $completionText = $res.CompletionMatches.CompletionText + $completionText -join ' ' | Should -BeExactly $Expected + + foreach ($match in $res.CompletionMatches) { + $toolTip = $match.ToolTip.Replace("""", "").Replace("'", "") + $completionText = $match.CompletionText.Replace("""", "").Replace("'", "") + $listItemText = $match.ListItemText + $toolTip.StartsWith($completionText) | Should -BeTrue + $toolTip.EndsWith($listItemText) | Should -BeTrue + } + } + + It "Should complete for '<TextInput>'" -Skip:(!$IsWindows) -TestCases @( + @{ TextInput = "Join-String -Separator '``"; Expected = $newlineSeparator } + @{ TextInput = "Join-String -Separator ""``"; Expected = $newlineSeparator } + @{ TextInput = "Join-String -Separator '``r"; Expected = $newlineSeparator } + @{ TextInput = "Join-String -Separator ""``r"; Expected = $newlineSeparator } + @{ TextInput = "Join-String -Separator '``r``"; Expected = $newlineSeparator } + @{ TextInput = "Join-String -Separator ""``r``"; Expected = $newlineSeparator } + @{ TextInput = "Join-String -FormatString '``"; Expected = $newlineFormatStrings } + @{ TextInput = "Join-String -FormatString ""``"; Expected = $newlineFormatStrings } + @{ TextInput = "Join-String -FormatString '``r"; Expected = $newlineFormatStrings } + @{ TextInput = "Join-String -FormatString ""``r"; Expected = $newlineFormatStrings } + @{ TextInput = "Join-String -FormatString '``r``"; Expected = $newlineFormatStrings } + @{ TextInput = "Join-String -FormatString ""``r``"; Expected = $newlineFormatStrings } + ) { + param($TextInput, $Expected) + $res = TabExpansion2 -inputScript $TextInput -cursorColumn $TextInput.Length + $completionText = $res.CompletionMatches.CompletionText + $completionText -join ' ' | Should -BeExactly $Expected + + foreach ($match in $res.CompletionMatches) { + $toolTip = $match.ToolTip.Replace("""", "").Replace("'", "") + $completionText = $match.CompletionText.Replace("""", "").Replace("'", "") + $listItemText = $match.ListItemText + $toolTip.StartsWith($completionText) | Should -BeTrue + $toolTip.EndsWith($listItemText) | Should -BeTrue + } + } + + It "Should complete for '<TextInput>'" -Skip:($IsWindows) -TestCases @( + @{ TextInput = "Join-String -Separator '``"; Expected = $newlineSeparator } + @{ TextInput = "Join-String -Separator ""``"; Expected = $newlineSeparator } + @{ TextInput = "Join-String -Separator '``n"; Expected = $newlineSeparator } + @{ TextInput = "Join-String -Separator ""``n"; Expected = $newlineSeparator } + @{ TextInput = "Join-String -FormatString '``"; Expected = $newlineFormatStrings } + @{ TextInput = "Join-String -FormatString ""``"; Expected = $newlineFormatStrings } + @{ TextInput = "Join-String -FormatString '``n"; Expected = $newlineFormatStrings } + @{ TextInput = "Join-String -FormatString ""``n"; Expected = $newlineFormatStrings } + ) { + param($TextInput, $Expected) + $res = TabExpansion2 -inputScript $TextInput -cursorColumn $TextInput.Length + $completionText = $res.CompletionMatches.CompletionText + $completionText -join ' ' | Should -BeExactly $Expected + + foreach ($match in $res.CompletionMatches) { + $toolTip = $match.ToolTip.Replace("""", "").Replace("'", "") + $completionText = $match.CompletionText.Replace("""", "").Replace("'", "") + $listItemText = $match.ListItemText + $toolTip.StartsWith($completionText) | Should -BeTrue + $toolTip.EndsWith($listItemText) | Should -BeTrue + } + } + } + Context "Format cmdlet's View paramter completion" { BeforeAll { $viewDefinition = @' @@ -1578,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) @@ -1643,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 { @@ -2063,6 +2464,42 @@ param ($Param1) Remove-Item -LiteralPath $LiteralPath } + + It "Should add single quotes if there are double quotes in bare word file path" { + $BadQuote = [char]8220 + $TestFile1 = Join-Path -Path $TestDrive -ChildPath "Test1${BadQuote}File" + $null = New-Item -Path $TestFile1 -Force + $res = TabExpansion2 -inputScript "Get-ChildItem -Path $TestDrive\" + ($res.CompletionMatches | Where-Object ListItemText -Like "Test1?File").CompletionText | Should -Be "'$TestFile1'" + Remove-Item -LiteralPath $TestFile1 -Force + } + + It "Should escape double quote if the input string uses double quotes" { + $BadQuote = [char]8220 + $TestFile1 = Join-Path -Path $TestDrive -ChildPath "Test1${BadQuote}File" + $null = New-Item -Path $TestFile1 -Force + $res = TabExpansion2 -inputScript "Get-ChildItem -Path `"$TestDrive\" + $Expected = "`"$($TestFile1.Insert($TestFile1.LastIndexOf($BadQuote), '`'))`"" + ($res.CompletionMatches | Where-Object ListItemText -Like "Test1?File").CompletionText | Should -Be $Expected + Remove-Item -LiteralPath $TestFile1 -Force + } + + It "Should escape single quotes in file paths" { + $SingleQuote = "'" + $TestFile1 = Join-Path -Path $TestDrive -ChildPath "Test1${SingleQuote}File" + $null = New-Item -Path $TestFile1 -Force + # Regardless if the input string was singlequoted or not, we expect to add surrounding single quotes and + # escape the single quote in the file path with another singlequote. + $Expected = "'$($TestFile1.Insert($TestFile1.LastIndexOf($SingleQuote), "'"))'" + + $res = TabExpansion2 -inputScript "Get-ChildItem -Path '$TestDrive\" + ($res.CompletionMatches | Where-Object ListItemText -Like "Test1?File").CompletionText | Should -Be $Expected + + $res = TabExpansion2 -inputScript "Get-ChildItem -Path $TestDrive\" + ($res.CompletionMatches | Where-Object ListItemText -Like "Test1?File").CompletionText | Should -Be $Expected + + Remove-Item -LiteralPath $TestFile1 -Force + } } It 'Should correct slashes in UNC path completion' -Skip:(!$IsWindows) { @@ -3575,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.inheritance.tests.ps1 b/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1 index 391375b0011..6b24b8b6ce0 100644 --- a/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1 +++ b/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1 @@ -715,3 +715,209 @@ Describe 'Base type has abstract properties' -Tags "CI" { $failure.Exception.Message | Should -BeLike "*'get_Exists'*" } } + +Describe 'Classes inheritance with protected and protected internal members in base class' -Tags 'CI' { + + BeforeAll { + Set-StrictMode -Version 3 + $c1DefinitionProtectedInternal = @' + public class C1ProtectedInternal + { + protected internal string InstanceField = "C1_InstanceField"; + protected internal string InstanceProperty { get; set; } = "C1_InstanceProperty"; + protected internal string InstanceMethod() { return "C1_InstanceMethod"; } + + protected internal virtual string VirtualProperty1 { get; set; } = "C1_VirtualProperty1"; + protected internal virtual string VirtualProperty2 { get; set; } = "C1_VirtualProperty2"; + protected internal virtual string VirtualMethod1() { return "C1_VirtualMethod1"; } + protected internal virtual string VirtualMethod2() { return "C1_VirtualMethod2"; } + + public string CtorUsed { get; set; } + public C1ProtectedInternal() { CtorUsed = "default ctor"; } + protected internal C1ProtectedInternal(string p1) { CtorUsed = "C1_ctor_1args:" + p1; } + } +'@ + $c2DefinitionProtectedInternal = @' + class C2ProtectedInternal : C1ProtectedInternal { + C2ProtectedInternal() : base() { $this.VirtualProperty2 = 'C2_VirtualProperty2' } + C2ProtectedInternal([string]$p1) : base($p1) { $this.VirtualProperty2 = 'C2_VirtualProperty2' } + + [string]GetInstanceField() { return $this.InstanceField } + [string]SetInstanceField([string]$value) { $this.InstanceField = $value; return $this.InstanceField } + [string]GetInstanceProperty() { return $this.InstanceProperty } + [string]SetInstanceProperty([string]$value) { $this.InstanceProperty = $value; return $this.InstanceProperty } + [string]CallInstanceMethod() { return $this.InstanceMethod() } + + [string]GetVirtualProperty1() { return $this.VirtualProperty1 } + [string]SetVirtualProperty1([string]$value) { $this.VirtualProperty1 = $value; return $this.VirtualProperty1 } + [string]CallVirtualMethod1() { return $this.VirtualMethod1() } + + [string]$VirtualProperty2 + [string]VirtualMethod2() { return 'C2_VirtualMethod2' } + # Note: Overriding a virtual property in a derived PowerShell class prevents access to the + # base property via simple typecast ([base]$this).VirtualProperty2. + [string]GetVirtualProperty2() { return $this.VirtualProperty2 } + [string]SetVirtualProperty2([string]$value) { $this.VirtualProperty2 = $value; return $this.VirtualProperty2 } + [string]CallVirtualMethod2Base() { return ([C1ProtectedInternal]$this).VirtualMethod2() } + [string]CallVirtualMethod2Derived() { return $this.VirtualMethod2() } + + [string]GetInstanceMemberDynamic([string]$name) { return $this.$name } + [string]SetInstanceMemberDynamic([string]$name, [string]$value) { $this.$name = $value; return $this.$name } + [string]CallInstanceMemberDynamic([string]$name) { return $this.$name() } + } + + [C2ProtectedInternal] +'@ + + Add-Type -TypeDefinition $c1DefinitionProtectedInternal + Add-Type -TypeDefinition (($c1DefinitionProtectedInternal -creplace 'C1ProtectedInternal', 'C1Protected') -creplace 'protected internal', 'protected') + + $testCases = @( + @{ accessType = 'protected'; derivedType = Invoke-Expression ($c2DefinitionProtectedInternal -creplace 'ProtectedInternal', 'Protected') } + @{ accessType = 'protected internal'; derivedType = Invoke-Expression $c2DefinitionProtectedInternal } + ) + } + + AfterAll { + Set-StrictMode -Off + } + + Context 'Derived class can access instance base class members' { + + It 'can call protected internal .NET method Object.MemberwiseClone()' { + class CNetMethod { + [string]$Foo + [object]CloneIt() { return $this.MemberwiseClone() } + } + $c1 = [CNetMethod]::new() + $c1.Foo = 'bar' + $c2 = $c1.CloneIt() + $c2.Foo | Should -Be 'bar' + } + + It 'can call <accessType> base ctor' -TestCases $testCases { + param($derivedType) + $derivedType::new('foo').CtorUsed | Should -Be 'C1_ctor_1args:foo' + } + + It 'can access <accessType> base field' -TestCases $testCases { + param($derivedType) + $c2 = $derivedType::new() + $c2.GetInstanceField() | Should -Be 'C1_InstanceField' + $c2.SetInstanceField('foo_InstanceField') | Should -Be 'foo_InstanceField' + } + + It 'can access <accessType> base property' -TestCases $testCases { + param($derivedType) + $c2 = $derivedType::new() + $c2.GetInstanceProperty() | Should -Be 'C1_InstanceProperty' + $c2.SetInstanceProperty('foo_InstanceProperty') | Should -Be 'foo_InstanceProperty' + } + + It 'can call <accessType> base method' -TestCases $testCases { + param($derivedType) + $derivedType::new().CallInstanceMethod() | Should -Be 'C1_InstanceMethod' + } + + It 'can access <accessType> virtual base property' -TestCases $testCases { + param($derivedType) + $c2 = $derivedType::new() + $c2.GetVirtualProperty1() | Should -Be 'C1_VirtualProperty1' + $c2.SetVirtualProperty1('foo_VirtualProperty1') | Should -Be 'foo_VirtualProperty1' + } + + It 'can call <accessType> virtual base method' -TestCases $testCases { + param($derivedType) + $derivedType::new().CallVirtualMethod1() | Should -Be 'C1_VirtualMethod1' + } + } + + Context 'Derived class can override virtual base class members' { + + It 'can override <accessType> virtual base property' -TestCases $testCases { + param($derivedType) + $c2 = $derivedType::new() + $c2.GetVirtualProperty2() | Should -Be 'C2_VirtualProperty2' + $c2.SetVirtualProperty2('foo_VirtualProperty2') | Should -Be 'foo_VirtualProperty2' + } + + It 'can override <accessType> virtual base method' -TestCases $testCases { + param($derivedType) + $c2 = $derivedType::new() + $c2.CallVirtualMethod2Base() | Should -Be 'C1_VirtualMethod2' + $c2.CallVirtualMethod2Derived() | Should -Be 'C2_VirtualMethod2' + } + } + + Context 'Derived class can access instance base class members dynamically' { + + It 'can access <accessType> base fields and properties' -TestCases $testCases { + param($derivedType) + $c2 = $derivedType::new() + $c2.GetInstanceMemberDynamic('InstanceField') | Should -Be 'C1_InstanceField' + $c2.GetInstanceMemberDynamic('InstanceProperty') | Should -Be 'C1_InstanceProperty' + $c2.GetInstanceMemberDynamic('VirtualProperty1') | Should -Be 'C1_VirtualProperty1' + $c2.SetInstanceMemberDynamic('InstanceField', 'foo1') | Should -Be 'foo1' + $c2.SetInstanceMemberDynamic('InstanceProperty', 'foo2') | Should -Be 'foo2' + $c2.SetInstanceMemberDynamic('VirtualProperty1', 'foo3') | Should -Be 'foo3' + } + + It 'can call <accessType> base methods' -TestCases $testCases { + param($derivedType) + $c2 = $derivedType::new() + $c2.CallInstanceMemberDynamic('InstanceMethod') | Should -Be 'C1_InstanceMethod' + $c2.CallInstanceMemberDynamic('VirtualMethod1') | Should -Be 'C1_VirtualMethod1' + } + } + + Context 'Base class members are not accessible outside class scope' { + + BeforeAll { + $instanceTest = { + $c2 = $derivedType::new() + { $null = $c2.InstanceField } | Should -Throw -ErrorId 'PropertyNotFoundStrict' + { $null = $c2.InstanceProperty } | Should -Throw -ErrorId 'PropertyNotFoundStrict' + { $null = $c2.VirtualProperty1 } | Should -Throw -ErrorId 'PropertyNotFoundStrict' + { $c2.InstanceField = 'foo' } | Should -Throw -ErrorId 'PropertyAssignmentException' + { $c2.InstanceProperty = 'foo' } | Should -Throw -ErrorId 'PropertyAssignmentException' + { $c2.VirtualProperty1 = 'foo' } | Should -Throw -ErrorId 'PropertyAssignmentException' + { $derivedType::new().InstanceMethod() } | Should -Throw -ErrorId 'MethodNotFound' + { $derivedType::new().VirtualMethod1() } | Should -Throw -ErrorId 'MethodNotFound' + foreach ($name in @('InstanceField', 'InstanceProperty', 'VirtualProperty1')) { + { $null = $c2.$name } | Should -Throw -ErrorId 'PropertyNotFoundStrict' + { $c2.$name = 'foo' } | Should -Throw -ErrorId 'PropertyAssignmentException' + } + foreach ($name in @('InstanceMethod', 'VirtualMethod1')) { + { $c2.$name() } | Should -Throw -ErrorId 'MethodNotFound' + } + } + $c3UnrelatedType = Invoke-Expression @" + class C3Unrelated { + [void]RunInstanceTest([type]`$derivedType) { $instanceTest } + } + [C3Unrelated] +"@ + $negativeTestCases = $testCases.ForEach({ + $item = $_.Clone() + $item['scopeType'] = 'null scope' + $item['classScope'] = $null + $item + $item = $_.Clone() + $item['scopeType'] = 'unrelated class scope' + $item['classScope'] = $c3UnrelatedType + $item + }) + } + + It 'cannot access <accessType> instance base members in <scopeType>' -TestCases $negativeTestCases { + param($derivedType, $classScope) + if ($null -eq $classScope) { + $instanceTest.Invoke() + } + else { + $c3 = $classScope::new() + $c3.RunInstanceTest($derivedType) + } + } + } +} 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/CommonParameters.Tests.ps1 b/test/powershell/Language/Scripting/CommonParameters.Tests.ps1 index 6f9d80e1677..4b6eda1ef95 100644 --- a/test/powershell/Language/Scripting/CommonParameters.Tests.ps1 +++ b/test/powershell/Language/Scripting/CommonParameters.Tests.ps1 @@ -147,6 +147,44 @@ Describe "Common parameters support for script cmdlets" -Tags "CI" { } } + Context "ProgressAction" { + It "Ignores progress actions on advanced script function with no variables" { + $ps.AddScript( +@' +function test-function { + [CmdletBinding()]param() + + Write-Progress "progress foo" +} +test-function -ProgressAction Ignore +'@).Invoke() + + $ps.Streams.Progress.Count | Should -Be 0 + $ps.Streams.Error | ForEach-Object { + Write-Error -ErrorRecord $_ -ErrorAction Stop + } + } + + It "Ignores progress actions on advanced script function with variables" { + $ps.AddScript( +@' +function test-function { + [CmdletBinding()]param([string]$path) + + switch($false) { default { "echo $path" } } + + Write-Progress "progress foo" +} +test-function -ProgressAction Ignore +'@).Invoke() + + $ps.Streams.Progress.Count | Should -Be 0 + $ps.Streams.Error | ForEach-Object { + Write-Error -ErrorRecord $_ -ErrorAction Stop + } + } + } + Context "SupportShouldprocess" { $script = ' function get-foo 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 2cc21dc03f3..19dc80caf28 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Core/Import-Module.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Core/Import-Module.Tests.ps1 @@ -57,23 +57,12 @@ Describe "Import-Module" -Tags "CI" { (Get-Module TestModule).Version | Should -BeIn "1.1" } - - It 'Should override default command prefix if an empty string is provided' { - $ModuleName = "PrefixTestModule" - $ModulePath = Join-Path -Path $testdrive -ChildPath $ModuleName - $null = New-Item -Path $ModulePath -ItemType Directory -Force - 'function Use-Something{}' | Set-Content -Path "$ModulePath\$ModuleName.psm1" -Force - New-ModuleManifest -Path "$ModulePath\$ModuleName.psd1" -DefaultCommandPrefix MyPrefix -FunctionsToExport Use-Something -RootModule "$ModuleName.psm1" - $ImportedModule = Import-Module -Name "$ModulePath\$ModuleName.psd1" -Prefix "" -PassThru - $ImportedModule.ExportedCommands.ContainsKey('Use-Something') | Should -Be $true - Remove-Module -ModuleInfo $ImportedModule - } - It 'ProcessorArchitecture should work' { $currentProcessorArchitecture = switch ([System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture) { '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/Set-PSDebug.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Core/Set-PSDebug.Tests.ps1 index 1bf610dd1f1..297e494d4d2 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Core/Set-PSDebug.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Core/Set-PSDebug.Tests.ps1 @@ -15,5 +15,16 @@ Describe "Set-PSDebug" -Tags "CI" { It "Should be able to set strict" { { Set-PSDebug -Strict } | Should -Not -Throw } + + It "Should skip magic extents created by pwsh" { + class ClassWithDefaultCtor { + MyMethod() { } + } + + { + Set-PSDebug -Trace 1 + [ClassWithDefaultCtor]::new() + } | Should -Not -Throw + } } } 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/FileSystem.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/FileSystem.Tests.ps1 index b365862f9d0..3899ace7e3b 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/FileSystem.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/FileSystem.Tests.ps1 @@ -605,11 +605,12 @@ Describe "Hard link and symbolic link tests" -Tags "CI", "RequireAdminOnWindows" } } - $realFile = Join-Path $TestPath "file.txt" + # Ensure that the file link can still be successfully created when the target file/directory name contains wildcards. + $realFile = Join-Path $TestPath "[file].txt" $nonFile = Join-Path $TestPath "not-a-file" $fileContent = "some text" - $realDir = Join-Path $TestPath "subdir" - $realDir2 = Join-Path $TestPath "second-subdir" + $realDir = Join-Path $TestPath "[subdir]" + $realDir2 = Join-Path $TestPath "[second-subdir]" $nonDir = Join-Path $TestPath "not-a-dir" $hardLinkToFile = Join-Path $TestPath "hard-to-file.txt" $symLinkToFile = Join-Path $TestPath "sym-link-to-file.txt" @@ -626,7 +627,7 @@ Describe "Hard link and symbolic link tests" -Tags "CI", "RequireAdminOnWindows" Context "New-Item and hard/symbolic links" { AfterEach { # clean up created links after each test - Remove-Item -Exclude (Split-Path -Leaf $realFile, $realDir, $realDir2) -Recurse $TestPath/* + Remove-Item -Exclude (Split-Path -Leaf ([WildcardPattern]::Escape($realFile)), ([WildcardPattern]::Escape($realDir)), ([WildcardPattern]::Escape($realDir2))) -Recurse $TestPath/* } It "New-Item can create a hard link to a file" { @@ -639,7 +640,7 @@ Describe "Hard link and symbolic link tests" -Tags "CI", "RequireAdminOnWindows" It "New-Item can create symbolic link to file" { New-Item -ItemType SymbolicLink -Path $symLinkToFile -Value $realFile > $null Test-Path $symLinkToFile | Should -BeTrue - $real = Get-Item -Path $realFile + $real = Get-Item -LiteralPath $realFile $link = Get-Item -Path $symLinkToFile $link.LinkType | Should -BeExactly "SymbolicLink" $link.Target | Should -BeExactly $real.ToString() @@ -658,7 +659,7 @@ Describe "Hard link and symbolic link tests" -Tags "CI", "RequireAdminOnWindows" It "New-Item can create a symbolic link to a directory" -Skip:($IsWindows) { New-Item -ItemType SymbolicLink -Path $symLinkToDir -Value $realDir > $null Test-Path $symLinkToDir | Should -BeTrue - $real = Get-Item -Path $realDir + $real = Get-Item -LiteralPath $realDir $link = Get-Item -Path $symLinkToDir $link.LinkType | Should -BeExactly "SymbolicLink" $link.Target | Should -BeExactly $real.ToString() @@ -666,7 +667,7 @@ Describe "Hard link and symbolic link tests" -Tags "CI", "RequireAdminOnWindows" It "New-Item can create a directory symbolic link to a directory" -Skip:(-Not $IsWindows) { New-Item -ItemType SymbolicLink -Path $symLinkToDir -Value $realDir > $null Test-Path $symLinkToDir | Should -BeTrue - $real = Get-Item -Path $realDir + $real = Get-Item -LiteralPath $realDir $link = Get-Item -Path $symLinkToDir $link | Should -BeOfType System.IO.DirectoryInfo $link.LinkType | Should -BeExactly "SymbolicLink" @@ -677,7 +678,7 @@ Describe "Hard link and symbolic link tests" -Tags "CI", "RequireAdminOnWindows" $target = Split-Path -Leaf $realDir New-Item -ItemType SymbolicLink -Path $symLinkToDir -Value $target > $null Test-Path $symLinkToDir | Should -BeTrue - $real = Get-Item -Path $realDir + $real = Get-Item -LiteralPath $realDir $link = Get-Item -Path $symLinkToDir $link | Should -BeOfType System.IO.DirectoryInfo $link.LinkType | Should -BeExactly "SymbolicLink" @@ -689,7 +690,7 @@ Describe "Hard link and symbolic link tests" -Tags "CI", "RequireAdminOnWindows" $target = ".\$(Split-Path -Leaf $realDir)" New-Item -ItemType SymbolicLink -Path $symLinkToDir -Value $target > $null Test-Path $symLinkToDir | Should -BeTrue - $real = Get-Item -Path $realDir + $real = Get-Item -LiteralPath $realDir $link = Get-Item -Path $symLinkToDir $link | Should -BeOfType System.IO.DirectoryInfo $link.LinkType | Should -BeExactly "SymbolicLink" @@ -732,8 +733,8 @@ Describe "Hard link and symbolic link tests" -Tags "CI", "RequireAdminOnWindows" } It "New-Item -Force can overwrite a junction" -Skip:(-Not $IsWindows){ - $rd2 = Get-Item -Path $realDir2 - New-Item -Name testfile.txt -ItemType file -Path $realDir + $rd2 = Get-Item -LiteralPath $realDir2 + New-Item -Name testfile.txt -ItemType file -Path ([WildcardPattern]::Escape($realDir)) New-Item -ItemType Junction -Path $junctionToDir -Value $realDir > $null Test-Path $junctionToDir | Should -BeTrue { New-Item -ItemType Junction -Path $junctionToDir -Value $realDir -ErrorAction Stop > $null } | Should -Throw -ErrorId "DirectoryNotEmpty,Microsoft.PowerShell.Commands.NewItemCommand" @@ -879,7 +880,7 @@ Describe "Hard link and symbolic link tests" -Tags "CI", "RequireAdminOnWindows" Remove-Item -Path $Link -ErrorAction SilentlyContinue > $null Test-Path -Path $Link | Should -BeFalse - Test-Path -Path $Target | Should -BeTrue + Test-Path -LiteralPath $Target | Should -BeTrue } } @@ -1205,6 +1206,20 @@ Describe "Extended FileSystem Item/Content Cmdlet Provider Tests" -Tags "Feature $result.Name | Should -BeExactly $testDir } + It "Verify Directory creation when path relative to current PSDrive is empty" { + try { + $rootDir = New-Item "NewDirectory" -ItemType Directory -Force + $rootPath = $rootDir.FullName + $newPSDrive = New-PSDrive -Name "NewPSDrive" -PSProvider FileSystem -Root $rootPath + + $result = New-Item -Path "NewPSDrive:\" -ItemType Directory -Force + $result.FullName.TrimEnd("/\") | Should -BeExactly $newPSDrive.Root + } + finally { + Remove-PSDrive -Name "NewPSDrive" -Force -ErrorAction SilentlyContinue + } + } + It "Verify File + Value" { $result = New-Item -Path . -ItemType File -Name $testFile -Value "Some String" $content = Get-Content -Path $testFile diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Get-Service.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Get-Service.Tests.ps1 index 9429229c537..04882a1aaf7 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Get-Service.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Get-Service.Tests.ps1 @@ -82,3 +82,60 @@ Describe "Get-Service cmdlet tests" -Tags "CI" { { & $script } | Should -Throw -ErrorId $errorid } } + +Describe 'Get-Service Admin tests' -Tag CI,RequireAdminOnWindows { + BeforeAll { + $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() + if ( -not $IsWindows ) { + $PSDefaultParameterValues["it:skip"] = $true + } + } + AfterAll { + $global:PSDefaultParameterValues = $originalDefaultParameterValues + } + + BeforeEach { + $serviceParams = @{ + Name = "PowerShellTest-$([Guid]::NewGuid().Guid)" + BinaryPathName = "$env:SystemRoot\System32\cmd.exe" + StartupType = 'Manual' + } + $service = New-Service @serviceParams + } + AfterEach { + $service | Remove-Service + } + + It "Ignores invalid description MUI entry" { + $serviceRegPath = "HKLM:\SYSTEM\CurrentControlSet\Services\$($service.Name)" + Set-ItemProperty -LiteralPath $serviceRegPath -Name Description -Value '@Fake.dll,-0' + $actual = Get-Service -Name $service.Name -ErrorAction Stop + + $actual.Name | Should -Be $service.Name + $actual.Status | Should -Be Stopped + $actual.Description | Should -BeNullOrEmpty + $actual.UserName | Should -Be LocalSystem + $actual.StartupType | Should -Be Manual + } + + It "Ignores no SERVICE_QUERY_CONFIG access" { + $sddl = ((sc.exe sdshow $service.Name) -join "").Trim() + $sd = ConvertFrom-SddlString -Sddl $sddl + $sd.RawDescriptor.DiscretionaryAcl.AddAccess( + [System.Security.AccessControl.AccessControlType]::Deny, + [System.Security.Principal.WindowsIdentity]::GetCurrent().User, + 0x1, # SERVICE_QUERY_CONFIG + [System.Security.AccessControl.InheritanceFlags]::None, + [System.Security.AccessControl.PropagationFlags]::None) + $newSddl = $sd.RawDescriptor.GetSddlForm([System.Security.AccessControl.AccessControlSections]::All) + $null = sc.exe sdset $service.Name $newSddl + + $actual = Get-Service -Name $service.Name -ErrorAction Stop + + $actual.Name | Should -Be $service.Name + $actual.Status | Should -Be Stopped + $actual.Description | Should -BeNullOrEmpty + $actual.UserName | Should -BeNullOrEmpty + $actual.StartupType | Should -Be InvalidValue + } +} 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/Trace-Command.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Trace-Command.Tests.ps1 index cdccd0e9ed3..533f3fc2d0c 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Trace-Command.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Trace-Command.Tests.ps1 @@ -84,6 +84,171 @@ Describe "Trace-Command" -tags "CI" { } } + Context "MethodInvocation traces" { + + BeforeAll { + $filePath = Join-Path $TestDrive 'testtracefile.txt' + + class MyClass { + MyClass() {} + MyClass([int]$arg) {} + + [void]Method() { return } + [void]Method([string]$arg) { return } + [void]Method([int]$arg) { return } + + [string]ReturnMethod() { return "foo" } + + static [void]StaticMethod() { return } + static [void]StaticMethod([string]$arg) { return } + } + + # C# classes support more features than pwsh classes + Add-Type -TypeDefinition @' +namespace TraceCommandTests; + +public sealed class OverloadTests +{ + public int PropertySetter { get; set; } + + public OverloadTests() {} + public OverloadTests(int value) + { + PropertySetter = value; + } + + public void GenericMethod<T>() + {} + + public T GenericMethodWithArg<T>(T obj) => obj; + + public void MethodWithDefault(string arg1, int optional = 1) + {} + + public void MethodWithOut(out int val) + { + val = 1; + } + + public void MethodWithRef(ref int val) + { + val = 1; + } +} +'@ + } + + AfterEach { + Remove-Item $filePath -Force -ErrorAction SilentlyContinue + } + + It "Traces instance method" { + $myClass = [MyClass]::new() + Trace-Command -Name MethodInvocation -Expression { + $myClass.Method(1) + } -FilePath $filePath + Get-Content $filePath | Should -BeLike "*Invoking method: void Method(int arg)" + } + + It "Traces static method" { + Trace-Command -Name MethodInvocation -Expression { + [MyClass]::StaticMethod(1) + } -FilePath $filePath + Get-Content $filePath | Should -BeLike "*Invoking method: static void StaticMethod(string arg)" + } + + It "Traces method with return type" { + $myClass = [MyClass]::new() + Trace-Command -Name MethodInvocation -Expression { + $myClass.ReturnMethod() + } -FilePath $filePath + Get-Content $filePath | Should -BeLike "*Invoking method: string ReturnMethod()" + } + + It "Traces constructor" { + Trace-Command -Name MethodInvocation -Expression { + [TraceCommandTests.OverloadTests]::new("1234") + } -FilePath $filePath + Get-Content $filePath | Should -BeLike "*Invoking method: TraceCommandTests.OverloadTests new(int value)" + } + + It "Traces Property setter invoked as a method" { + $obj = [TraceCommandTests.OverloadTests]::new() + Trace-Command -Name MethodInvocation -Expression { + $obj.set_PropertySetter(1234) + } -FilePath $filePath + Get-Content $filePath | Should -BeLike "*Invoking method: void set_PropertySetter(int value)" + } + + It "Traces generic method" { + $obj = [TraceCommandTests.OverloadTests]::new() + Trace-Command -Name MethodInvocation -Expression { + $obj.GenericMethod[int]() + } -FilePath $filePath + Get-Content $filePath | Should -BeLike "*Invoking method: void GenericMethod``[int``]()" + } + + It "Traces generic method with argument" { + $obj = [TraceCommandTests.OverloadTests]::new() + Trace-Command -Name MethodInvocation -Expression { + $obj.GenericMethodWithArg("foo") + } -FilePath $filePath + Get-Content $filePath | Should -BeLike "*Invoking method: string GenericMethodWithArg``[string``](string obj)" + } + + It "Traces .NET call with default value" { + $obj = [TraceCommandTests.OverloadTests]::new() + Trace-Command -Name MethodInvocation -Expression { + $obj.MethodWithDefault("foo") + } -FilePath $filePath + Get-Content $filePath | Should -BeLike "*Invoking method: void MethodWithDefault(string arg1, int optional = 1)" + } + + It "Traces method with ref argument" { + $obj = [TraceCommandTests.OverloadTests]::new() + $v = 1 + + Trace-Command -Name MethodInvocation -Expression { + $obj.MethodWithRef([ref]$v) + } -FilePath $filePath + # [ref] goes through the binder so will trigger the first trace + Get-Content $filePath | Select-Object -Skip 1 | Should -BeLike "*Invoking method: void MethodWithRef(``[ref``] int val)" + } + + It "Traces method with out argument" { + $obj = [TraceCommandTests.OverloadTests]::new() + $v = 1 + + Trace-Command -Name MethodInvocation -Expression { + $obj.MethodWithOut([ref]$v) + } -FilePath $filePath + # [ref] goes through the binder so will trigger the first trace + Get-Content $filePath | Select-Object -Skip 1 | Should -BeLike "*Invoking method: void MethodWithOut(``[ref``] int val)" + } + + It "Traces a binding error" { + Trace-Command -Name MethodInvocation -Expression { + # try/catch is used as error formatter will hit the trace as well + try { + [System.Runtime.InteropServices.Marshal]::SizeOf([int]) + } + catch { + # Satisfy codefactor + $_ | Out-Null + } + } -FilePath $filePath + # type fqn is used, the wildcard avoids hardcoding that + Get-Content $filePath | Should -BeLike "*Invoking method: static int SizeOf``[System.RuntimeType, *``](System.RuntimeType, * structure)" + } + + It "Traces LINQ call" { + Trace-Command -Name MethodInvocation -Expression { + [System.Linq.Enumerable]::Union([int[]]@(1, 2), [int[]]@(3, 4)) + } -FilePath $filePath + Get-Content $filePath | Should -BeLike "*Invoking method: static System.Collections.Generic.IEnumerable``[int``] Union``[int``](System.Collections.Generic.IEnumerable``[int``] first, System.Collections.Generic.IEnumerable``[int``] second)" + } + } + Context "Trace-Command tests for code coverage" { BeforeAll { 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 2690c75fa9f..7c0fffa5c4a 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/WebCmdlets.Tests.ps1 @@ -284,7 +284,7 @@ function ExecuteWebRequest { return $result } -[string] $verboseEncodingPrefix = 'Content encoding: ' +[string] $debugEncodingPrefix = 'WebResponse content encoding: ' # This function calls Invoke-WebRequest with the given uri and # parses the verbose output to determine the encoding used for the content. function ExecuteRestMethod { @@ -297,37 +297,39 @@ function ExecuteRestMethod { $UseBasicParsing ) $result = @{Output = $null; Error = $null; Encoding = $null; Content = $null} - $verbosePreferenceSave = $VerbosePreference - $VerbosePreference = 'Continue' + $debugPreferenceSave = $DebugPreference + $DebugPreference = 'Continue' try { - $verboseFile = Join-Path $TestDrive -ChildPath ExecuteRestMethod.verbose.txt - $result.Output = Invoke-RestMethod -Uri $Uri -UseBasicParsing:$UseBasicParsing.IsPresent -Verbose 4>$verboseFile + $debugFile = Join-Path $TestDrive -ChildPath ExecuteRestMethod.debug.txt + $result.Output = Invoke-RestMethod -Uri $Uri -UseBasicParsing:$UseBasicParsing.IsPresent 5>$debugFile $result.Content = $result.Output - if (Test-Path -Path $verboseFile) { - $result.Verbose = Get-Content -Path $verboseFile - foreach ($item in $result.Verbose) { + # Invoke-RestMethod does not return encoding as part of an object, only in debug output, so we parse the debug output for the purposes of verifying the test. + # This debug output is defined in InvokeRestMethodCommand.Common.cs ProcessResponse() + if (Test-Path -Path $debugFile) { + $result.Debug = Get-Content -Path $debugFile + foreach ($item in $result.Debug) { $line = $item.Trim() - if ($line.StartsWith($verboseEncodingPrefix)) { - $encodingName = $item.SubString($verboseEncodingPrefix.Length).Trim() + if ($line.StartsWith($debugEncodingPrefix)) { + $encodingName = [int]::Parse($item.SubString($EncodingPrefix.Length).Split('CodePage: ')[1].Trim()) $result.Encoding = [System.Text.Encoding]::GetEncoding($encodingName) break } } - if ($result.Encoding -eq $null) { - throw "Encoding not found in verbose output. Lines: $($result.Verbose.Count) Content:$($result.Verbose)" + if ($null -eq $result.Encoding) { + throw "Encoding not found in debug output. Lines: $($result.Debug.Count) Content:$($result.Debug)" } } - if ($result.Verbose -eq $null) { - throw "No verbose output was found" + if ($null -eq $result.Debug) { + throw "No debug output was found" } } catch { $result.Error = $_ | Select-Object * | Out-String } finally { - $VerbosePreference = $verbosePreferenceSave - if (Test-Path -Path $verboseFile) { - Remove-Item -Path $verboseFile -ErrorAction SilentlyContinue + $DebugPreference = $debugPreferenceSave + if (Test-Path -Path $debugFile) { + Remove-Item -Path $debugFile -ErrorAction SilentlyContinue } } @@ -1725,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 @@ -3566,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/LanguagePrimitive.Tests.ps1 b/test/powershell/engine/Api/LanguagePrimitive.Tests.ps1 index bc9d812afe4..6149adc2ab7 100644 --- a/test/powershell/engine/Api/LanguagePrimitive.Tests.ps1 +++ b/test/powershell/engine/Api/LanguagePrimitive.Tests.ps1 @@ -185,4 +185,42 @@ Describe "Language Primitive Tests" -Tags "CI" { $test.TestHandlerReturnEnum() | Should -BeTrue $test.TestHandlerReturnObject() | Should -BeTrue } + + It 'Handles large numbers with thousands separators that previously failed' { + $formattedNumber = "9223372036854775,807" + $convertedValue = [System.Management.Automation.LanguagePrimitives]::ConvertTo($formattedNumber, [bigint]) + $convertedValue | Should -Be 9223372036854775807 + } + + It 'Handles extremely large numbers to verify precision' { + $formattedNumber = "99999999999999999999999999999" + $convertedValue = [System.Management.Automation.LanguagePrimitives]::ConvertTo($formattedNumber, [bigint]) + $convertedValue | Should -Be 99999999999999999999999999999 + } + + It 'Parses mixed separators correctly' { + $formattedNumber = "1,0000,00" + $convertedValue = [System.Management.Automation.LanguagePrimitives]::ConvertTo($formattedNumber, [bigint]) + $convertedValue | Should -Be 1000000 + } + + It 'Parses a number string using the invariant culture, irrespective of the current culture' { + $originalCulture = [cultureinfo]::CurrentCulture + try { + [cultureinfo]::CurrentCulture = [cultureinfo]::GetCultureInfo("de-DE") + $formattedNumber = "1.000" # in de-DE this means 1000 + $convertedValue = [System.Management.Automation.LanguagePrimitives]::ConvertTo($formattedNumber, [bigint]) + # since [bigint] uses invariant culture, this will be parsed as 1 + $convertedValue | Should -Be 1 + } + finally { + [cultureinfo]::CurrentCulture = $originalCulture + } + } + + It 'Casts from floating-point number string to BigInteger using fallback' { + $formattedNumber = "1.2" + $convertedValue = [System.Management.Automation.LanguagePrimitives]::ConvertTo($formattedNumber, [bigint]) + $convertedValue | Should -Be 1 + } } 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/GroupPolicySettings.Tests.ps1 b/test/powershell/engine/Basic/GroupPolicySettings.Tests.ps1 index 1986a270259..dd3943a8f81 100644 --- a/test/powershell/engine/Basic/GroupPolicySettings.Tests.ps1 +++ b/test/powershell/engine/Basic/GroupPolicySettings.Tests.ps1 @@ -1,7 +1,7 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. -Describe 'Group policy settings tests' -Tag CI,RequireAdminOnWindows { +Describe 'Group policy settings tests' -Tags @('CI', 'RequireAdminOnWindows') { BeforeAll { $originalDefaultParameterValues = $PSDefaultParameterValues.Clone() if ( ! $IsWindows ) { 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/NativeCommandErrorHandling.Tests.ps1 b/test/powershell/engine/Basic/NativeCommandErrorHandling.Tests.ps1 index 8122e598d2c..62696ec1692 100644 --- a/test/powershell/engine/Basic/NativeCommandErrorHandling.Tests.ps1 +++ b/test/powershell/engine/Basic/NativeCommandErrorHandling.Tests.ps1 @@ -48,7 +48,7 @@ Describe 'Native command error handling tests' -Tags 'CI' { $error[0].FullyQualifiedErrorId | Should -BeExactly 'ProgramExitedWithNonZeroCode' $error[0].TargetObject | Should -BeExactly $exePath - $stderr[1].Exception.Message | Should -BeExactly "Program `"$exeName`" ended with non-zero exit code: 1." + $stderr[1].Exception.Message | Should -BeExactly ("Program `"$exeName`" ended with non-zero exit code: 1 ({0})." -f ($IsWindows ? '0x00000001' : '0x01')) } It "Non-boolean value should not cause type casting error when the native command exited with non-zero code" { @@ -61,7 +61,7 @@ Describe 'Native command error handling tests' -Tags 'CI' { $error[0].FullyQualifiedErrorId | Should -BeExactly 'ProgramExitedWithNonZeroCode' $error[0].TargetObject | Should -BeExactly $exePath - $stderr[1].Exception.Message | Should -BeExactly "Program `"$exeName`" ended with non-zero exit code: 1." + $stderr[1].Exception.Message | Should -BeExactly ("Program `"$exeName`" ended with non-zero exit code: 1 ({0})." -f ($IsWindows ? '0x00000001' : '0x01')) } It 'Non-zero exit code generates a non-teminating error for $ErrorActionPreference = ''SilentlyContinue''' { 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/Adapter.Tests.ps1 b/test/powershell/engine/ETS/Adapter.Tests.ps1 index 7876dd778a6..f65b08f8174 100644 --- a/test/powershell/engine/ETS/Adapter.Tests.ps1 +++ b/test/powershell/engine/ETS/Adapter.Tests.ps1 @@ -200,6 +200,17 @@ Describe "Adapter Tests" -tags "CI" { # TODO: dynamic method calls } + + It "Can use PSForEach as an alias for the Foreach magic method" { + $x = 5 + $x.PSForEach({$_}) | Should -Be 5 + + $p = $null.PSForEach{"I didn't run"} + $p.GetType().Name | Should -BeExactly 'Collection`1' + $p.Count | Should -BeExactly 0 + + ([pscustomobject]@{ Name = 'bar' }).PSForEach({$_.Name}) | Should -BeExactly 'bar' + } } Context "Where Magic Method Adapter Tests" { @@ -240,6 +251,17 @@ Describe "Adapter Tests" -tags "CI" { } -PassThru -Force $x.Where(5) | Should -Be 10 } + + It "Can use PSWhere as an alias for the Where magic method" { + $x = 5 + $x.PSWhere{$true} | Should -Be 5 + + $p = $null.PSWhere{"I didn't run"} + $p.GetType().Name | Should -BeExactly 'Collection`1' + $p.Count | Should -BeExactly 0 + + ([pscustomobject]@{ Name = 'bar' }).PSWhere({$_.Name}) | ForEach-Object Name | Should -BeExactly 'bar' + } } } 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 439dc3be989..155654b3b80 100644 --- a/test/powershell/engine/Help/UpdatableHelpSystem.Tests.ps1 +++ b/test/powershell/engine/Help/UpdatableHelpSystem.Tests.ps1 @@ -47,7 +47,7 @@ else } # default values for system modules -[string] $myUICulture = 'en-US' +[string] $myUICulture = 'en-US' [string] $HelpInstallationPath = Join-Path $PSHOME $myUICulture [string] $HelpInstallationPathHome = Join-Path $userHelpRoot $myUICulture @@ -118,7 +118,7 @@ else } "Microsoft.PowerShell.Utility" = @{ - HelpFiles = "Microsoft.PowerShell.Commands.Utility.dll-Help.xml", "Microsoft.PowerShell.Utility-help.xml" + HelpFiles = "Microsoft.PowerShell.Commands.Utility.dll-Help.xml" HelpInfoFiles = "Microsoft.PowerShell.Utility_1da87e53-152b-403e-98dc-74d7b4d63d59_HelpInfo.xml" CompressedFiles = "Microsoft.PowerShell.Utility_1da87e53-152b-403e-98dc-74d7b4d63d59_en-US_helpcontent$extension" HelpInstallationPath = $HelpInstallationPath @@ -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/Help/assets/Microsoft.PowerShell.Utility_1da87e53-152b-403e-98dc-74d7b4d63d59_en-US_helpcontent.cab b/test/powershell/engine/Help/assets/Microsoft.PowerShell.Utility_1da87e53-152b-403e-98dc-74d7b4d63d59_en-US_helpcontent.cab index 949471140ce..ec0d3294f79 100644 Binary files a/test/powershell/engine/Help/assets/Microsoft.PowerShell.Utility_1da87e53-152b-403e-98dc-74d7b4d63d59_en-US_helpcontent.cab and b/test/powershell/engine/Help/assets/Microsoft.PowerShell.Utility_1da87e53-152b-403e-98dc-74d7b4d63d59_en-US_helpcontent.cab differ diff --git a/test/powershell/engine/Help/assets/Microsoft.PowerShell.Utility_1da87e53-152b-403e-98dc-74d7b4d63d59_en-US_helpcontent.zip b/test/powershell/engine/Help/assets/Microsoft.PowerShell.Utility_1da87e53-152b-403e-98dc-74d7b4d63d59_en-US_helpcontent.zip index 47643e90de6..480f920114f 100644 Binary files a/test/powershell/engine/Help/assets/Microsoft.PowerShell.Utility_1da87e53-152b-403e-98dc-74d7b4d63d59_en-US_helpcontent.zip and b/test/powershell/engine/Help/assets/Microsoft.PowerShell.Utility_1da87e53-152b-403e-98dc-74d7b4d63d59_en-US_helpcontent.zip differ diff --git a/test/powershell/engine/Module/IsolatedModule.Tests.ps1 b/test/powershell/engine/Module/IsolatedModule.Tests.ps1 index 00dae01838c..08729386ced 100644 --- a/test/powershell/engine/Module/IsolatedModule.Tests.ps1 +++ b/test/powershell/engine/Module/IsolatedModule.Tests.ps1 @@ -4,8 +4,6 @@ Describe "Isolated module scenario - load the whole module in custom ALC" -Tag 'CI' { It "Loading 'IsolatedModule' should work as expected" { - Set-ItResult -Pending -Because "The test is failing as we cannot depend on Newtonsoft.Json v10.0.0 as it has security vulnerabilities." - ## The 'IsolatedModule' module can be found at '<repo-root>\test\tools\Modules'. ## The module assemblies are created and deployed by '<repo-root>\test\tools\TestAlc'. ## The module defines its own custom ALC and has its module structure organized in a special way that allows the module to be loaded in that custom ALC. @@ -14,9 +12,15 @@ Describe "Isolated module scenario - load the whole module in custom ALC" -Tag ' ## │ Test.Isolated.Init.dll (contains the custom ALC and code to setup 'Resolving' handler) ## │ ## └───Dependencies - ## Newtonsoft.Json.dll (version 10.0.0.0 dependency) + ## System.CommandLine.dll (version 2.0.0.0 dependency) ## Test.Isolated.Nested.dll (nested binary module) ## Test.Isolated.Root.dll (root binary module) + + ## The assembly 'System.CommandLine.dll' should not be loaded in the PowerShell default ALC by default. + [System.Runtime.Loader.AssemblyLoadContext]::Default.Assemblies | + Where-Object FullName -Like 'System.CommandLine,*' | + Should -Be $null + $module = Import-Module IsolatedModule -PassThru $nestedCmd = Get-Command Test-NestedCommand $rootCmd = Get-Command Test-RootCommand @@ -30,10 +34,14 @@ Describe "Isolated module scenario - load the whole module in custom ALC" -Tag ' $context1.Name | Should -BeExactly "MyCustomALC" $context1 | Should -Be $context2 - ## Test-NestedCommand depends on NewtonSoft.Json 10.0.0.0 while PowerShell depends on 13.0.0.0 or higher. - ## The exact version of NewtonSoft.Json should be loaded to the custom ALC. + ## Test-NestedCommand depends on 'System.CommandLine.dll' which is loaded in the module's ALC. $foo = [Test.Isolated.Nested.Foo]::new("Hello", "World") - Test-NestedCommand -Param $foo | Should -BeExactly "Hello-World-Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed" + Test-NestedCommand -Param $foo | Should -BeExactly "Hello-World-System.CommandLine, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" + + ## The assembly 'System.CommandLine.dll' should still not be loaded in the PowerShell default ALC. + [System.Runtime.Loader.AssemblyLoadContext]::Default.Assemblies | + Where-Object FullName -Like 'System.CommandLine,*' | + Should -Be $null ## The type 'Test.Isolated.Root.Red' from the root module can be resolved and should be from the same load context. $context3 = [System.Runtime.Loader.AssemblyLoadContext]::GetLoadContext($rootCmd.ImplementingType.Assembly) diff --git a/test/powershell/engine/Remoting/CustomConnection.Tests.ps1 b/test/powershell/engine/Remoting/CustomConnection.Tests.ps1 index 2749f3bd28e..a8d4652ae93 100644 --- a/test/powershell/engine/Remoting/CustomConnection.Tests.ps1 +++ b/test/powershell/engine/Remoting/CustomConnection.Tests.ps1 @@ -27,14 +27,14 @@ function Start-PwshProcess Describe 'NamedPipe Custom Remote Connection Tests' -Tags 'Feature','RequireAdminOnWindows' { - BeforeAll { + BeforeEach { Import-Module -Name Microsoft.PowerShell.NamedPipeConnection -ErrorAction Stop $script:PwshProcId = Start-PwshProcess $script:session = $null } - AfterAll { + AfterEach { if ($null -ne $script:session) { Remove-PSSession -Session $script:session @@ -57,6 +57,10 @@ Describe 'NamedPipe Custom Remote Connection Tests' -Tags 'Feature','RequireAdmi # Skip this timeout test for non-Windows platforms, because dotNet named pipes do not honor the 'NumberOfServerInstances' # property and allows connection to a currently connected server. It 'Verifies timeout error when trying to connect to pwsh process with current connection' -Skip:(!$IsWindows) { + # We start an active connection to have it block the second connection attempt. + $script:session = New-NamedPipeSession -ProcessId $script:PwshProcId -ConnectingTimeout 10 -Name CustomNPConnection -ErrorAction Stop + + # The above connection means the named pipe server is busy and won't allow this second connection. $brokenSession = New-NamedPipeSession -ProcessId $script:PwshProcId -ConnectingTimeout 2 -Name CustomNPConnection -ErrorAction Stop # Verify expected broken session @@ -66,4 +70,29 @@ Describe 'NamedPipe Custom Remote Connection Tests' -Tags 'Feature','RequireAdmi $brokenSession | Remove-PSSession } + + It 'Passes $using: with PSv5 compatibility in Invoke-Command' { + $script:session = New-NamedPipeSession -ProcessId $script:PwshProcId -ConnectingTimeout 10 -Name CustomNPConnection -ErrorAction Stop + + Function Test-Function { + 'foo' + } + + # The v2 engine will choke on a var with '-' in the name and the v3/v4 + # using logic will revert to the v2 branch if $using is in a new scope. + # By using a function and a new scope we can verify the v5 logic is + # used and not the v2-4 one. + $result = Invoke-Command -Session $script:session -ScriptBlock { + ${function:Test-Function} = ${using:function:Test-Function} + + Test-Function + + # Running in a new scope triggers the v2 logic if the v3/v4 branch + # was used. + & { (${using:function:Test-Function}).Trim() } + } + $result.Count | Should -Be 2 + $result[0] | Should -BeExactly foo + $result[1] | Should -BeExactly "'foo'" + } } 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 80af8002bda..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 @@ -551,7 +551,8 @@ function Get-HelpNetworkTestCases # Command discovery does not follow symlinks to network locations for module qualified paths $networkBlockedError = "CommandNameNotAllowed,Microsoft.PowerShell.Commands.GetHelpCommand" - $scriptBlockedError = "ScriptsNotAllowed" + # This error may change as long as no test cases start failing for other reasons + $scriptBlockedError = "CommandNotFoundException" $formats = @( '//{0}/share/{1}' 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/NestedCommand.cs b/test/tools/TestAlc/nested/NestedCommand.cs index 88822920138..9b2628d1aeb 100644 --- a/test/tools/TestAlc/nested/NestedCommand.cs +++ b/test/tools/TestAlc/nested/NestedCommand.cs @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.CommandLine; using System.Management.Automation; -using Newtonsoft.Json; namespace Test.Isolated.Nested { @@ -14,7 +14,7 @@ public class TestNestedCommand : PSCmdlet protected override void ProcessRecord() { - WriteObject($"{Param.Name}-{Param.Path}-{typeof(StringEscapeHandling).Assembly.FullName}"); + WriteObject($"{Param.Name}-{Param.Path}-{typeof(RootCommand).Assembly.FullName}"); } } diff --git a/test/tools/TestAlc/nested/Test.Isolated.Nested.csproj b/test/tools/TestAlc/nested/Test.Isolated.Nested.csproj index cd9e87f5bed..886b5f11826 100644 --- a/test/tools/TestAlc/nested/Test.Isolated.Nested.csproj +++ b/test/tools/TestAlc/nested/Test.Isolated.Nested.csproj @@ -3,13 +3,15 @@ <Import Project="..\..\..\Test.Common.props" /> <PropertyGroup> + <!-- Suppress language folders --> + <SatelliteResourceLanguages>en</SatelliteResourceLanguages> + <!-- Disable PDB generation --> <DebugSymbols>false</DebugSymbols> <DebugType>None</DebugType> <!-- Disable deps.json generation --> <GenerateDependencyFile>false</GenerateDependencyFile> - <NoWarn>NU1901;NU1902;NU1903;NU1904</NoWarn> <!-- Deploy the produced assembly --> <PublishDir>..\..\Modules\IsolatedModule\Dependencies</PublishDir> @@ -17,7 +19,7 @@ <ItemGroup> <PackageReference Include="PowerShellStandard.Library" Version="5.1.1" PrivateAssets="All" /> - <PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> + <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 7d06d8897f8..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.2.25163.3" /> - <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 a582db86d78..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.3" /> + <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_CompletionHelpers.cs b/test/xUnit/csharp/test_CompletionHelpers.cs index c91a478fd85..c428213a8e6 100644 --- a/test/xUnit/csharp/test_CompletionHelpers.cs +++ b/test/xUnit/csharp/test_CompletionHelpers.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System; using System.Management.Automation; using Xunit; @@ -38,6 +39,18 @@ public class CompletionHelpersTests [InlineData("key$word", "'", "'key$word'")] [InlineData("key$word", "", "'key$word'")] [InlineData("key$word", "\"", "\"key$word\"")] + [InlineData("`r`n", "\"", "\"`r`n\"")] + [InlineData("`r`n", "'", "\"`r`n\"")] + [InlineData("`r`n", "", "\"`r`n\"")] + [InlineData("`r`n `${0}", "\"", "\"`r`n `${0}\"")] + [InlineData("`r`n `${0}", "'", "\"`r`n `${0}\"")] + [InlineData("`r`n `${0}", "", "\"`r`n `${0}\"")] + [InlineData("`n", "\"", "\"`n\"")] + [InlineData("`n", "'", "\"`n\"")] + [InlineData("`n", "", "\"`n\"")] + [InlineData("`n `${0}", "\"", "\"`n `${0}\"")] + [InlineData("`n `${0}", "'", "\"`n `${0}\"")] + [InlineData("`n `${0}", "", "\"`n `${0}\"")] public void TestQuoteCompletionText( string completionText, string quote, @@ -66,6 +79,10 @@ public void TestQuoteCompletionText( [InlineData("\"", true)] [InlineData("'", true)] [InlineData("", true)] + [InlineData(";", true)] + [InlineData("; ", true)] + [InlineData(",", true)] + [InlineData(", ", true)] public void TestCompletionRequiresQuotes(string completion, bool expected) { bool result = CompletionHelpers.CompletionRequiresQuotes(completion); @@ -92,5 +109,75 @@ public void TestHandleDoubleAndSingleQuote(string wordToComplete, string expecte Assert.Equal(expectedQuote, quote); Assert.Equal(expectedWordToComplete, wordToComplete); } + + [Theory] + [InlineData("word", "word", true)] + [InlineData("Word", "word", true)] + [InlineData("word", "wor", true)] + [InlineData("word", "words", false)] + [InlineData("word`nnext", "word`n", true)] + [InlineData("word`r`nnext", "word`r`n", true)] + [InlineData("word;next", "word;", true)] + [InlineData("word,next", "word,", true)] + [InlineData("word[*]next", "word[*", true)] + [InlineData("word[abc]next", "word[abc", true)] + [InlineData("word", "word*", true)] + [InlineData("Word", "word*", true)] + [InlineData("word(Special)", "word*", true)] + [InlineData("testword", "test", true)] + [InlineData("word", "", true)] + [InlineData("", "word", false)] + public void TestDefaultMatch(string value, string wordToComplete, bool expected) + { + bool result = CompletionHelpers.DefaultMatch(value, wordToComplete); + Assert.Equal(expected, result); + } + + [Theory] + [InlineData("word", "word", true)] + [InlineData("Word", "word", true)] + [InlineData("word", "wor", true)] + [InlineData("word", "words", false)] + [InlineData("word`nnext", "word`n", true)] + [InlineData("word`r`nnext", "word`r`n", true)] + [InlineData("word;next", "word;", true)] + [InlineData("word,next", "word,", true)] + [InlineData("word[*]next", "word[*", true)] + [InlineData("word[abc]next", "word[abc", true)] + [InlineData("word", "word*", false)] + [InlineData("Word", "word*", false)] + [InlineData("word(Special)", "word*", false)] + [InlineData("testword", "test", true)] + [InlineData("word", "", true)] + [InlineData("", "word", false)] + public void TestWildcardPatternEscapeMatch(string value, string wordToComplete, bool expected) + { + bool result = CompletionHelpers.WildcardPatternEscapeMatch(value, wordToComplete); + Assert.Equal(expected, result); + } + + [Theory] + [InlineData("\n", "`n")] + [InlineData("\r", "`r")] + [InlineData("\r\n", "`r`n")] + [InlineData("\t", "`t")] + [InlineData("\0", "`0")] + [InlineData("\a", "`a")] + [InlineData("\b", "`b")] + [InlineData("\u001b", "`e")] + [InlineData("\f", "`f")] + [InlineData("\v", "`v")] + [InlineData("word\n", "word`n")] + [InlineData("word\r", "word`r")] + [InlineData("word\t", "word`t")] + [InlineData("word\u001b", "word`e")] + [InlineData("word\f", "word`f")] + [InlineData("word\v", "word`v")] + [InlineData("word\r\n", "word`r`n")] + public void TestNormalizeToExpandableString(string value, string expected) + { + string result = CompletionHelpers.NormalizeToExpandableString(value); + Assert.Equal(expected, result); + } } } 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 b843ec370ec..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.0.2"> + <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.13.0" /> + <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 eb4f1035136..a0746028a56 100644 --- a/tools/cgmanifest.json +++ b/tools/cgmanifest/tpn/cgmanifest.json @@ -55,7 +55,7 @@ "Type": "nuget", "Nuget": { "Name": "JsonSchema.Net", - "Version": "7.3.4" + "Version": "7.4.0" } }, "DevelopmentDependency": false @@ -65,7 +65,7 @@ "Type": "nuget", "Nuget": { "Name": "Markdig.Signed", - "Version": "0.40.0" + "Version": "0.45.0" } }, "DevelopmentDependency": false @@ -85,7 +85,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Bcl.AsyncInterfaces", - "Version": "9.0.3" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -105,7 +105,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.CodeAnalysis.Common", - "Version": "4.13.0" + "Version": "5.0.0" } }, "DevelopmentDependency": false @@ -115,7 +115,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.CodeAnalysis.CSharp", - "Version": "4.13.0" + "Version": "5.0.0" } }, "DevelopmentDependency": false @@ -125,7 +125,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Extensions.ObjectPool", - "Version": "9.0.3" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -165,7 +165,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Win32.Registry.AccessControl", - "Version": "9.0.3" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -175,7 +175,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Win32.SystemEvents", - "Version": "9.0.3" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -185,7 +185,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Windows.Compatibility", - "Version": "9.0.3" + "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.3" + "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.3" + "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.3" + "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.3" + "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.3" + "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.3" + "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.3" + "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.3" + "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.3" + "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.3" + "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.3" + "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.3" + "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.3" + "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.3" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -355,7 +355,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.native.System.IO.Ports", - "Version": "9.0.3" + "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.3" + "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.3" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -435,7 +435,7 @@ "Type": "nuget", "Nuget": { "Name": "System.CodeDom", - "Version": "9.0.3" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -445,7 +445,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ComponentModel.Composition.Registration", - "Version": "9.0.3" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -455,7 +455,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ComponentModel.Composition", - "Version": "9.0.3" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -465,7 +465,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Configuration.ConfigurationManager", - "Version": "9.0.3" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -475,7 +475,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Data.Odbc", - "Version": "9.0.3" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -485,7 +485,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Data.OleDb", - "Version": "9.0.3" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -505,7 +505,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Diagnostics.EventLog", - "Version": "9.0.3" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -515,7 +515,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Diagnostics.PerformanceCounter", - "Version": "9.0.3" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -525,7 +525,7 @@ "Type": "nuget", "Nuget": { "Name": "System.DirectoryServices.AccountManagement", - "Version": "9.0.3" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -535,7 +535,7 @@ "Type": "nuget", "Nuget": { "Name": "System.DirectoryServices.Protocols", - "Version": "9.0.3" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -545,7 +545,7 @@ "Type": "nuget", "Nuget": { "Name": "System.DirectoryServices", - "Version": "9.0.3" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -555,7 +555,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Drawing.Common", - "Version": "9.0.3" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -565,7 +565,7 @@ "Type": "nuget", "Nuget": { "Name": "System.IO.Packaging", - "Version": "9.0.3" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -575,7 +575,7 @@ "Type": "nuget", "Nuget": { "Name": "System.IO.Ports", - "Version": "9.0.3" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -585,7 +585,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Management", - "Version": "9.0.3" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -595,17 +595,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Net.Http.WinHttpHandler", - "Version": "9.0.3" - } - }, - "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.3" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -625,7 +615,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Runtime.Caching", - "Version": "9.0.3" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -635,7 +625,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Cryptography.Pkcs", - "Version": "9.0.3" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -645,7 +635,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Cryptography.ProtectedData", - "Version": "9.0.3" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -655,7 +645,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Cryptography.Xml", - "Version": "9.0.3" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -665,7 +655,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Permissions", - "Version": "9.0.3" + "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.3" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -735,7 +715,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ServiceProcess.ServiceController", - "Version": "9.0.3" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -745,17 +725,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Speech", - "Version": "9.0.3" - } - }, - "DevelopmentDependency": false - }, - { - "Component": { - "Type": "nuget", - "Nuget": { - "Name": "System.Threading.AccessControl", - "Version": "9.0.3" + "Version": "10.0.3" } }, "DevelopmentDependency": false @@ -765,7 +735,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Web.Services.Description", - "Version": "8.1.1" + "Version": "8.1.2" } }, "DevelopmentDependency": false @@ -775,7 +745,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Windows.Extensions", - "Version": "9.0.3" + "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 2a9434c9cbe..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').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" | 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 fd740b4ec2b..b2c8b561502 100644 --- a/tools/metadata.json +++ b/tools/metadata.json @@ -1,10 +1,10 @@ { - "StableReleaseTag": "v7.5.0", - "PreviewReleaseTag": "v7.6.0-preview.3", + "StableReleaseTag": "v7.6.1", + "PreviewReleaseTag": "v7.7.0-preview.1", "ServicingReleaseTag": "v7.0.13", - "ReleaseTag": "v7.5.0", - "LTSReleaseTag" : ["v7.4.7"], - "NextReleaseTag": "v7.6.0-preview.4", - "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 18b25f019f9..8d8154e7445 100644 --- a/tools/packaging/boms/windows.json +++ b/tools/packaging/boms/windows.json @@ -1,3522 +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" + "FileType": "NonProduct", + "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", + "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", + "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 f8961cc4883..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 + } } - 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 '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 + } + } + } 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 + } } +} + +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 + } - return $Arguments + # 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 - 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,25 +3861,54 @@ 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. + # If you submit a package without this information to the Store, the Store will silently modify your package to include it. + # To find the PhoneProductId value, you need to run a package through the Store certification process, + # and use the PhoneProductId value from the Store certified package to update the manifest in your source code. + # This is the PhoneProductId for the "Microsoft.PowerShell" package. + $PhoneProductId = "5b3ae196-2df7-446e-8060-94b4ad878387" + $isPreview = Test-IsPreview -Version $ProductSemanticVersion if ($isPreview) { + # 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' $appxManifest = Get-Content "$RepoRoot\assets\AppxManifest.xml" -Raw - $appxManifest = $appxManifest.Replace('$VERSION$', $ProductVersion).Replace('$ARCH$', $Architecture).Replace('$PRODUCTNAME$', $productName).Replace('$DISPLAYNAME$', $displayName).Replace('$PUBLISHER$', $releasePublisher) + $appxManifest = $appxManifest. + Replace('$VERSION$', $ProductVersion). + Replace('$ARCH$', $Architecture). + Replace('$PRODUCTNAME$', $productName). + Replace('$DISPLAYNAME$', $displayName). + Replace('$PUBLISHER$', $releasePublisher). + Replace('$PHONEPRODUCTID$', $PhoneProductId) + $xml = [xml]$appxManifest if ($isPreview) { Write-Verbose -Verbose "Adding pwsh-preview.exe alias" @@ -3748,7 +3938,6 @@ function New-MSIXPackage else { Copy-Item -Path "$RepoRoot\assets\$_.png" -Destination "$ProductSourcePath\assets\" } - } if ($PSCmdlet.ShouldProcess("Create .msix package?")) { @@ -3761,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" } } @@ -4304,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))) { @@ -4968,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 @@ -4982,6 +5188,9 @@ class BomRecord { [string] $FileType = "NonProduct" + [string[]] + $Architecture + # Add methods to normalize Pattern to use `/` as the directory separator, # but give a Pattern that is usable on the current platform [string] @@ -5011,6 +5220,13 @@ class BomRecord { # If the directory separator character is a slash, then set the pattern as-is $this.Pattern = $Pattern } + + [void] + EnsureArchitecture([string[]]$DefaultArchitecture = @("x64","x86","arm64")) { + if (-not $this.PSObject.Properties.Match("Architecture")) { + $this.Architecture = $DefaultArchitecture + } + } } # Verify a folder based on a BOM json. @@ -5024,7 +5240,9 @@ function Test-Bom { [string] $Path, [switch] - $Fix + $Fix, + [string] + $Architecture ) Write-Log "verifying no unauthorized files have been added or removed..." 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/releaseBuild/setReleaseTag.ps1 b/tools/releaseBuild/setReleaseTag.ps1 new file mode 100644 index 00000000000..c5f2f016554 --- /dev/null +++ b/tools/releaseBuild/setReleaseTag.ps1 @@ -0,0 +1,161 @@ +param( + [Parameter(HelpMessage='ReleaseTag from the job. Set to "fromBranch" or $null to update using the branch name')] + [string]$ReleaseTag, + + [Parameter(HelpMessage='The branch name used to update the release tag.')] + [string]$Branch=$env:BUILD_SOURCEBRANCH, + + [Parameter(HelpMessage='The variable name to put the new release tagin.')] + [string]$Variable='ReleaseTag', + + [switch]$CreateJson +) + +function New-BuildInfoJson { + param( + [parameter(Mandatory = $true)] + [string] + $ReleaseTag, + [switch] $IsDaily + ) + + $blobName = $ReleaseTag -replace '\.', '-' + + $isPreview = $ReleaseTag -like '*-*' + + $filename = 'stable.json' + if($isPreview) + { + $filename = 'preview.json' + } + if($IsDaily.IsPresent) + { + $filename = 'daily.json' + } + + ## Get the UTC time and round up to the second. + $dateTime = [datetime]::UtcNow + $dateTime = [datetime]::new($dateTime.Ticks - ($dateTime.Ticks % [timespan]::TicksPerSecond), $dateTime.Kind) + + @{ + ReleaseTag = $ReleaseTag + ReleaseDate = $dateTime + BlobName = $blobName + BaseUrl = 'https://powershellinfraartifacts-gkhedzdeaghdezhr.z01.azurefd.net/install' + } | ConvertTo-Json | Out-File -Encoding ascii -Force -FilePath $filename + + $resolvedPath = (Resolve-Path -Path $filename).ProviderPath + $vstsCommandString = "vso[task.setvariable variable=BuildInfoPath]$resolvedPath" + Write-Verbose -Message "$vstsCommandString" -Verbose + Write-Host -Object "##$vstsCommandString" + + # Upload for ADO pipelines + Write-Host "##vso[artifact.upload containerfolder=BuildInfoJson;artifactname=BuildInfoJson]$resolvedPath" + + # Copy to location where OneBranch Pipelines uploads from + + # if the environment variable does not exist, we are not in OneBranch. So just return. + if (-not $env:ob_outputDirectory) { + return + } + + if (-not (Test-Path $env:ob_outputDirectory)) { + $null = New-Item -Path $env:ob_outputDirectory -ItemType Directory -Force -Verbose + } + + Copy-Item $resolvedPath -Destination $env:ob_outputDirectory -Force -Verbose +} + +# Script to set the release tag based on the branch name if it is not set or it is "fromBranch" +# the branch name is expected to be release-<semver> or <previewname> +# VSTS passes it as 'refs/heads/release-v6.0.2' + +$branchOnly = $Branch -replace '^refs/heads/'; +$branchOnly = $branchOnly -replace '[_\-]' + +$msixType = 'preview' + +$isDaily = $false + +if($ReleaseTag -eq 'fromBranch' -or !$ReleaseTag) +{ + # Branch is named release-<semver> + $releaseBranchRegex = '^.*((release/|rebuild/.*rebuild))' + if($Branch -match $releaseBranchRegex) + { + $msixType = 'release' + Write-Verbose "release branch:" -Verbose + $releaseTag = $Branch -replace '^.*((release|rebuild)/)' + $vstsCommandString = "vso[task.setvariable variable=$Variable]$releaseTag" + Write-Verbose -Message "setting $Variable to $releaseTag" -Verbose + Write-Host -Object "##$vstsCommandString" + + if ($CreateJson.IsPresent) + { + New-BuildInfoJson -ReleaseTag $releaseTag + } + } + elseif(($branchOnly -eq 'master' -and $env:BUILD_REASON -ne 'Manual') -or $branchOnly -like '*dailytest*') + { + $isDaily = $true + Write-Verbose "daily build" -Verbose + $jsonPath = "${env:SYSTEM_ARTIFACTSDIRECTORY}\BuildInfoJson\daily.json" + if (test-path -Path $jsonPath) { + Write-Verbose "restoring from buildinfo json..." -Verbose + $buildInfo = Get-Content -Path $jsonPath | ConvertFrom-Json + $releaseTag = $buildInfo.ReleaseTag + } else { + Write-Verbose "creating from branch counter and metadata.json..." -Verbose + $metaDataJsonPath = Join-Path $PSScriptRoot -ChildPath '..\metadata.json' + $metadata = Get-Content $metaDataJsonPath | ConvertFrom-Json + $versionPart = $metadata.PreviewReleaseTag + if ($versionPart -match '-.*$') { + $versionPart = $versionPart -replace '-.*$' + } + + $releaseTag = "$versionPart-daily$((Get-Date).ToString('yyyyMMdd')).$($env:BRANCHCOUNTER)" + } + + $vstsCommandString = "vso[task.setvariable variable=$Variable]$releaseTag" + Write-Verbose -Message "setting $Variable to $releaseTag" -Verbose + Write-Host -Object "##$vstsCommandString" + + if ($CreateJson.IsPresent) + { + New-BuildInfoJson -ReleaseTag $releaseTag -IsDaily + } + } + else + { + Write-Verbose "non-release branch" -Verbose + # Branch is named <previewname> + # Get version from metadata and append -<previewname> + $metaDataJsonPath = Join-Path $PSScriptRoot -ChildPath '..\metadata.json' + $metadata = Get-Content $metaDataJsonPath | ConvertFrom-Json + $versionPart = $metadata.PreviewReleaseTag + if($versionPart -match '-.*$') + { + $versionPart = $versionPart -replace '-.*$' + } + + $releaseTag = "$versionPart-$branchOnly" + $vstsCommandString = "vso[task.setvariable variable=$Variable]$releaseTag" + Write-Verbose -Message "setting $Variable to $releaseTag" -Verbose + Write-Host -Object "##$vstsCommandString" + + if ($CreateJson.IsPresent) + { + New-BuildInfoJson -ReleaseTag $releaseTag + } + } +} + +$vstsCommandString = "vso[task.setvariable variable=IS_DAILY]$($isDaily.ToString().ToLowerInvariant())" +Write-Verbose -Message "$vstsCommandString" -Verbose +Write-Host -Object "##$vstsCommandString" + +$vstsCommandString = "vso[task.setvariable variable=MSIX_TYPE]$msixType" +Write-Verbose -Message "$vstsCommandString" -Verbose +Write-Host -Object "##$vstsCommandString" + +Write-Output $releaseTag diff --git a/tools/releaseBuild/setReleaseTag.sh b/tools/releaseBuild/setReleaseTag.sh new file mode 100644 index 00000000000..842ba1e755b --- /dev/null +++ b/tools/releaseBuild/setReleaseTag.sh @@ -0,0 +1 @@ +pwsh -command ".\setReleaseTag.ps1 $*" 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 } diff --git a/tools/super-linter/config/super-linter.env b/tools/super-linter/config/super-linter.env new file mode 100644 index 00000000000..e7324b0feb9 --- /dev/null +++ b/tools/super-linter/config/super-linter.env @@ -0,0 +1,8 @@ +VALIDATE_ALL_CODEBASE=false +DEFAULT_BRANCH=master +FILTER_REGEX_INCLUDE=.*\.md +VALIDATE_EDITORCONFIG=false +VALIDATE_JSCPD=false +VALIDATE_CHECKOV=false +FIX_MARKDOWN_PRETTIER=true +FIX_MARKDOWN=true diff --git a/tools/super-linter/super-linter.ps1 b/tools/super-linter/super-linter.ps1 new file mode 100644 index 00000000000..571ba9c7f8d --- /dev/null +++ b/tools/super-linter/super-linter.ps1 @@ -0,0 +1,15 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +param( + [string]$RepoRoot = (Join-Path -Path $PSScriptRoot -ChildPath '../..'), + [string]$Platform +) + +$resolvedPath = (Resolve-Path $RepoRoot).ProviderPath +$platformParam = @() +if ($Platform) { + $platformParam = @("--platform", $Platform) +} + +docker run $platformParam -e RUN_LOCAL=true --env-file "$PSScriptRoot/config/super-linter.env" -v "${resolvedPath}:/tmp/lint" ghcr.io/super-linter/super-linter:latest