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 efe9133c8ff..57d2f6c6c3e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -103,6 +103,8 @@ dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case # Suggest more modern language features when available dotnet_style_object_initializer = true:suggestion dotnet_style_collection_initializer = true:suggestion +# Background Info: https://github.com/dotnet/runtime/pull/100250 +dotnet_style_prefer_collection_expression = when_types_exactly_match dotnet_style_coalesce_expression = true:suggestion dotnet_style_null_propagation = true:suggestion dotnet_style_explicit_tuple_names = true:suggestion @@ -117,6 +119,17 @@ csharp_prefer_simple_default_expression = true:suggestion dotnet_code_quality_unused_parameters = non_public:suggestion +# Dotnet diagnostic settings: +[*.cs] + +# CA1859: Use concrete types when possible for improved performance +# 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 26e01101693..14569b81924 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -3,75 +3,66 @@ # 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 -# Area: Portability -# @JamesWTruher - # Area: Security -# @TravisEz13 @PaulHigin -src/System.Management.Automation/security/wldpNativeMethods.cs @TravisEz13 @PaulHigin - -# Area: Documentation -.github/ @joeyaiello @TravisEz13 +src/System.Management.Automation/security/wldpNativeMethods.cs @TravisEz13 @seeminglyscience -# Area: Test -# @JamesWTruher @TravisEz13 @adityapatwardhan - -# Area: Cmdlets Core -# @JamesWTruher @SteveL-MSFT @anmenaga +# Area: CI Build +.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 # but the feature seems to make taking a union of all the matching rules. # Area: Cmdlets Management -src/Microsoft.PowerShell.Commands.Management/ @daxian-dbw @adityapatwardhan +# src/Microsoft.PowerShell.Commands.Management/ @daxian-dbw @adityapatwardhan # Area: Utility Cmdlets -src/Microsoft.PowerShell.Commands.Utility/ @JamesWTruher @PaulHigin +# src/Microsoft.PowerShell.Commands.Utility/ # Area: Console -src/Microsoft.PowerShell.ConsoleHost/ @daxian-dbw @anmenaga @TylerLeonhardt - -# Area: Demos -demos/ @joeyaiello @SteveL-MSFT @HemantMahawar +# src/Microsoft.PowerShell.ConsoleHost/ @daxian-dbw # Area: DSC -src/System.Management.Automation/DscSupport @TravisEz13 @SteveL-MSFT +# src/System.Management.Automation/DscSupport @TravisEz13 @SteveL-MSFT # Area: Engine # src/System.Management.Automation/engine @daxian-dbw # Area: Debugging # Must be below engine to override -src/System.Management.Automation/engine/debugger/ @PaulHigin +# src/System.Management.Automation/engine/debugger/ # Area: Help -src/System.Management.Automation/help @adityapatwardhan +src/System.Management.Automation/help @adityapatwardhan @daxian-dbw # Area: Intellisense # @daxian-dbw # Area: Language -src/System.Management.Automation/engine/parser @daxian-dbw +src/System.Management.Automation/engine/parser @daxian-dbw @seeminglyscience # Area: Providers -src/System.Management.Automation/namespaces @anmenaga +# src/System.Management.Automation/namespaces # Area: Remoting -src/System.Management.Automation/engine/remoting @PaulHigin +src/System.Management.Automation/engine/remoting @daxian-dbw @TravisEz13 # Areas: Build # Must be last -*.config @daxian-dbw @TravisEz13 @adityapatwardhan @anmenaga @PaulHigin -*.props @daxian-dbw @TravisEz13 @adityapatwardhan @anmenaga @PaulHigin -*.yml @daxian-dbw @TravisEz13 @adityapatwardhan @anmenaga @PaulHigin -*.csproj @daxian-dbw @TravisEz13 @adityapatwardhan @anmenaga @PaulHigin -build.* @daxian-dbw @TravisEz13 @adityapatwardhan @anmenaga @PaulHigin -tools/ @daxian-dbw @TravisEz13 @adityapatwardhan @anmenaga @PaulHigin -docker/ @daxian-dbw @TravisEz13 @adityapatwardhan @anmenaga @PaulHigin +*.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/action-filters.yml b/.github/action-filters.yml new file mode 100644 index 00000000000..9a61bc1947b --- /dev/null +++ b/.github/action-filters.yml @@ -0,0 +1,23 @@ +github: &github + - .github/actions/** + - .github/workflows/**-ci.yml +tools: &tools + - tools/buildCommon/** + - tools/ci.psm1 +props: &props + - '**.props' +tests: &tests + - test/powershell/** + - test/tools/** + - test/xUnit/** +mainSource: &mainSource + - src/** +buildModule: &buildModule + - build.psm1 +source: + - *github + - *tools + - *props + - *buildModule + - *mainSource + - *tests diff --git a/.github/actions/build/ci/action.yml b/.github/actions/build/ci/action.yml new file mode 100644 index 00000000000..65331fb3185 --- /dev/null +++ b/.github/actions/build/ci/action.yml @@ -0,0 +1,40 @@ +name: CI Build +description: 'Builds PowerShell' +runs: + using: composite + steps: + - name: Capture Environment + if: success() || failure() + 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@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1 + with: + global-json-file: ./global.json + - 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 + - name: Build + if: success() + run: |- + Write-Verbose -Verbose "Running Build..." + Import-Module .\tools\ci.psm1 + Invoke-CIBuild + shell: pwsh + - name: Upload build artifact + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: build + path: ${{ runner.workspace }}/build 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 new file mode 100644 index 00000000000..ce37a38c8b7 --- /dev/null +++ b/.github/actions/test/linux-packaging/action.yml @@ -0,0 +1,69 @@ +name: linux_packaging +description: 'Linux packaging for PowerShell' + +runs: + using: composite + steps: + - name: Capture Environment + if: success() || failure() + run: |- + Import-Module ./tools/ci.psm1 + Show-Environment + shell: pwsh + + - uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0 + with: + 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: Build and Package + run: |- + Import-Module ./tools/ci.psm1 + $releaseTag = Get-ReleaseTag + Start-PSBuild -Configuration 'Release' -ReleaseTag $releaseTag + Invoke-CIFinish + shell: pwsh + + - name: Install Pester + run: |- + Import-Module ./tools/ci.psm1 + Install-CIPester + shell: pwsh + + - name: Validate Package Names + run: |- + # 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 new file mode 100644 index 00000000000..ab30e0d9ce6 --- /dev/null +++ b/.github/actions/test/nix/action.yml @@ -0,0 +1,158 @@ +name: nix_test +description: 'Test PowerShell on non-Windows platforms' + +inputs: + purpose: + required: false + default: '' + type: string + tagSet: + required: false + default: CI + type: string + ctrfFolder: + 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: |- + Import-Module ./tools/ci.psm1 + Show-Environment + shell: pwsh + + - name: Download Build Artifacts + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + path: "${{ github.workspace }}" + + - name: Capture Artifacts Directory + continue-on-error: true + 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@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@e69ef5462fd455e02edcaf4dd7708eda96b9eda0 # 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 + 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: |- + 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 + if: success() + 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-CITest -Purpose '${{ inputs.purpose }}' -TagSet '${{ inputs.tagSet }}' -TitlePrefix '${{ inputs.buildName }}' -OutputFormat NUnitXml + shell: pwsh + + - 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" + ctrfFolder: "${{ inputs.ctrfFolder }}" diff --git a/.github/actions/test/process-pester-results/action.yml b/.github/actions/test/process-pester-results/action.yml new file mode 100644 index 00000000000..44f2037626f --- /dev/null +++ b/.github/actions/test/process-pester-results/action.yml @@ -0,0 +1,27 @@ +name: process-pester-test-results +description: 'Process Pester test results' + +inputs: + name: + required: true + default: '' + type: string + testResultsFolder: + required: false + default: "${{ runner.workspace }}/testResults" + type: string + +runs: + using: composite + steps: + - name: Log Summary + run: |- + & "$env:GITHUB_ACTION_PATH/process-pester-results.ps1" -Name '${{ inputs.name }}' -TestResultsFolder '${{ inputs.testResultsFolder }}' + shell: pwsh + + - name: Upload testResults artifact + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: junit-pester-${{ inputs.name }} + path: ${{ runner.workspace }}/testResults diff --git a/.github/actions/test/process-pester-results/process-pester-results.ps1 b/.github/actions/test/process-pester-results/process-pester-results.ps1 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/windows/action.yml b/.github/actions/test/windows/action.yml new file mode 100644 index 00000000000..ddc5da4d664 --- /dev/null +++ b/.github/actions/test/windows/action.yml @@ -0,0 +1,107 @@ +name: windows_test +description: 'Test PowerShell on Windows' + +inputs: + purpose: + required: false + default: '' + type: string + tagSet: + required: false + default: CI + type: string + ctrfFolder: + 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: |- + Import-Module ./tools/ci.psm1 + Show-Environment + shell: pwsh + + - name: Download Build Artifacts + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + path: "${{ github.workspace }}" + + - name: Capture Artifacts Directory + continue-on-error: true + 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@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: |- + Import-Module ./build.psm1 + Write-LogGroupStart -Title 'Bootstrap' + 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 + Write-LogGroupEnd -Title 'Bootstrap' + + - name: Test + if: success() + run: |- + Import-Module .\build.psm1 -force + Import-Module .\tools\ci.psm1 + Restore-PSOptions -PSOptionsPath '${{ github.workspace }}\build\psoptions.json' + $options = (Get-PSOptions) + $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 NUnitXml + shell: pwsh + + - 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 + ctrfFolder: "${{ inputs.ctrfFolder }}" 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 7996891b5f7..45d2e8fe928 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,66 +1,31 @@ version: 2 updates: - - package-ecosystem: "nuget" + - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" labels: - "CL-BuildPackaging" - ignore: - - dependency-name: "System.*" - - dependency-name: "Microsoft.Win32.Registry.AccessControl" - - dependency-name: "Microsoft.Windows.Compatibility" - - - package-ecosystem: "nuget" - directory: "/tools/packaging/projects/reference/Microsoft.PowerShell.Commands.Utility" - schedule: - interval: "daily" - labels: - - "CL-BuildPackaging" - ignore: - - dependency-name: "System.*" - - dependency-name: "Microsoft.Win32.Registry.AccessControl" - - dependency-name: "Microsoft.Windows.Compatibility" - - package-ecosystem: "nuget" - directory: "/tools/packaging/projects/reference/System.Management.Automation" - schedule: - interval: "daily" - labels: - - "CL-BuildPackaging" - ignore: - - dependency-name: "System.*" - - dependency-name: "Microsoft.Win32.Registry.AccessControl" - - dependency-name: "Microsoft.Windows.Compatibility" - - - package-ecosystem: "nuget" - directory: "/test/tools/Modules" + - package-ecosystem: "github-actions" + directory: "/" + target-branch: "release/*" schedule: interval: "daily" labels: - "CL-BuildPackaging" - ignore: - - dependency-name: "System.*" - - dependency-name: "Microsoft.Win32.Registry.AccessControl" - - dependency-name: "Microsoft.Windows.Compatibility" - - package-ecosystem: "nuget" - directory: "/src/Modules" + - package-ecosystem: "docker" + directory: / schedule: - interval: "daily" + interval: daily labels: - "CL-BuildPackaging" - - package-ecosystem: "github-actions" + - package-ecosystem: "docker" directory: "/" - schedule: - interval: "daily" - labels: - - "CL-BuildPackaging" - - - package-ecosystem: docker - directory: / + target-branch: "release/*" schedule: interval: daily labels: 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/IssueManagement.ResolveStale.yml b/.github/policies/IssueManagement.ResolveStale.yml index 2994a2e38e8..fd254715ea9 100644 --- a/.github/policies/IssueManagement.ResolveStale.yml +++ b/.github/policies/IssueManagement.ResolveStale.yml @@ -33,6 +33,8 @@ configuration: - isOpen - isNotLabeledWith: label: KeepOpen + - isNotLabeledWith: + label: In-PR - isNotLabeledWith: label: Needs-Triage - isNotLabeledWith: 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 419d704ce1d..00000000000 --- a/.github/workflows/AssignPrs.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Auto Assign PR Maintainer -on: - pull_request: - types: [opened, edited] -permissions: - contents: read - -jobs: - run: - runs-on: ubuntu-latest - permissions: - issues: write - pull-requests: write - steps: - - uses: wow-actions/auto-assign@67fafa03df61d7e5f201734a2fa60d1ab111880d # v3.0.2 - 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/codeql-analysis.yml b/.github/workflows/analyze-reusable.yml similarity index 52% rename from .github/workflows/codeql-analysis.yml rename to .github/workflows/analyze-reusable.yml index fde3f8c7697..3ef564eba90 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/analyze-reusable.yml @@ -1,30 +1,30 @@ -name: "CodeQL" +name: CodeQL Analysis (Reusable) on: - push: - branches: [master] - pull_request: - # The branches below must be a subset of the branches above - branches: [master] + workflow_call: + inputs: + runner_os: + description: 'Runner OS for CodeQL analysis' + type: string + required: false + default: ubuntu-latest -defaults: - run: - shell: pwsh +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_SKIP_FIRST_TIME_EXPERIENCE: 1 - -permissions: - contents: read + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + DOTNET_NOLOGO: 1 + POWERSHELL_TELEMETRY_OPTOUT: 1 + __SuppressAnsiEscapeSequences: 1 + nugetMultiFeedWarnLevel: none jobs: 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 + runs-on: ${{ inputs.runner_os }} strategy: fail-fast: false @@ -37,13 +37,17 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + 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@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 + 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. @@ -52,18 +56,22 @@ jobs: # queries: ./path/to/local/query, your-org/your-repo/queries@main - run: | - Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose + 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 + Invoke-CIBuild -Configuration 'StaticAnalysis' name: Build + shell: pwsh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 + 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 3e8c0180b3d..00000000000 --- a/.github/workflows/createReminders.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: 'Create reminder' - -on: - issue_comment: - types: [created, edited] - -permissions: - contents: read - -jobs: - reminder: - 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@1bc8a409a8b377b781b2be426be54067b7a2dcab # v1.1.16 diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 74eee94fba4..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@4081bf99e2866ebe428fc0477b69eb4fcda7220a # v4.4.0 + uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml new file mode 100644 index 00000000000..27ceac59bbd --- /dev/null +++ b/.github/workflows/labels.yml @@ -0,0 +1,31 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +name: Verify PR Labels + +on: + pull_request: + types: [opened, reopened, edited, labeled, unlabeled, synchronize] + +permissions: + contents: read + pull-requests: read + +jobs: + verify-labels: + if: github.repository_owner == 'PowerShell' + runs-on: ubuntu-latest + + steps: + - name: Check out the repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Verify PR has label starting with 'cl-' + id: verify-labels + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + script: | + const labels = context.payload.pull_request.labels.map(label => label.name.toLowerCase()); + if (!labels.some(label => label.startsWith('cl-'))) { + core.setFailed("Every PR must have at least one label starting with 'cl-'."); + } diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml new file mode 100644 index 00000000000..77186125a9c --- /dev/null +++ b/.github/workflows/linux-ci.yml @@ -0,0 +1,262 @@ +name: Linux-CI + +run-name: "${{ github.ref_name }} - ${{ github.run_number }}" + +on: + workflow_dispatch: + + push: + branches: + - master + - release/** + - github-mirror + paths: + - "**" + - "*" + - ".globalconfig" + - "!.github/ISSUE_TEMPLATE/**" + - "!.dependabot/config.yml" + - "!.pipelines/**" + - "!test/perf/**" + pull_request: + branches: + - master + - release/** + - github-mirror + - "*-feature" +# Path filters for PRs need to go into the changes job + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }} + cancel-in-progress: ${{ contains(github.ref, 'merge')}} + +env: + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + DOTNET_NOLOGO: 1 + FORCE_FEATURE: 'False' + FORCE_PACKAGE: 'False' + NUGET_KEY: none + POWERSHELL_TELEMETRY_OPTOUT: 1 + __SuppressAnsiEscapeSequences: 1 + nugetMultiFeedWarnLevel: none + system_debug: 'false' +jobs: + changes: + if: startsWith(github.repository_owner, 'azure') || github.repository_owner == 'PowerShell' + name: Change Detection + runs-on: ubuntu-latest + # 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Change Detection + id: filter + uses: "./.github/actions/infrastructure/path-filters" + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + merge_conflict_check: + name: Check for Merge Conflict Markers + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' && (startsWith(github.repository_owner, 'azure') || github.repository_owner == 'PowerShell') + permissions: + pull-requests: read + contents: read + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Check for merge conflict markers + uses: "./.github/actions/infrastructure/merge-conflict-checker" + + ci_build: + name: Build PowerShell + runs-on: ubuntu-latest + needs: changes + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + + - name: Build + uses: "./.github/actions/build/ci" + linux_test_unelevated_ci: + name: Linux Unelevated CI + needs: + - ci_build + - changes + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + - name: Linux Unelevated CI + uses: "./.github/actions/test/nix" + 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' || needs.changes.outputs.buildModuleChanged == 'true' }} + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + - name: Linux Elevated CI + uses: "./.github/actions/test/nix" + 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' || needs.changes.outputs.buildModuleChanged == 'true' }} + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + - name: Linux Unelevated Others + uses: "./.github/actions/test/nix" + 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' || needs.changes.outputs.buildModuleChanged == 'true' }} + runs-on: ubuntu-latest + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + - name: Linux Elevated Others + uses: "./.github/actions/test/nix" + with: + purpose: ElevatedPesterTests + tagSet: Others + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + xunit_tests: + name: xUnit Tests + needs: + - changes + 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1 + + - 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 + + $config = New-PesterConfiguration + $config.Run.Path = './test/infrastructure/' + $config.Run.PassThru = $true + $config.TestResult.Enabled = $true + $config.TestResult.OutputFormat = 'NUnitXml' + $config.TestResult.OutputPath = "$testResultsFolder/InfrastructureTests.xml" + $config.Output.Verbosity = 'Detailed' + + $result = Invoke-Pester -Configuration $config + + if ($result.FailedCount -gt 0 -or $result.Result -eq 'Failed') { + throw "Infrastructure tests failed" + } + + - name: Publish Test Results + uses: "./.github/actions/test/process-pester-results" + if: always() + with: + name: "InfrastructureTests" + testResultsFolder: "${{ github.workspace }}/testResults" + + ## Temporarily disable the CodeQL analysis on Linux as it doesn't work for .NET SDK 10-rc.2. + # analyze: + # name: CodeQL Analysis + # needs: changes + # if: ${{ needs.changes.outputs.source == 'true' }} + # uses: ./.github/workflows/analyze-reusable.yml + # permissions: + # actions: read + # contents: read + # security-events: write + # with: + # runner_os: ubuntu-latest + + ready_to_merge: + name: Linux ready to merge + needs: + - xunit_tests + - linux_test_elevated_ci + - linux_test_elevated_others + - linux_test_unelevated_ci + - linux_test_unelevated_others + - linux_packaging + - merge_conflict_check + - infrastructure_tests + # - analyze + if: always() + uses: PowerShell/compliance/.github/workflows/ready-to-merge.yml@c8b3ad5819ad7078f3e375519b4f8c6232d1cbdf # v1.0.0 + with: + needs_context: ${{ toJson(needs) }} + 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 new file mode 100644 index 00000000000..55d852bb68a --- /dev/null +++ b/.github/workflows/macos-ci.yml @@ -0,0 +1,248 @@ +name: macOS-CI + +run-name: "${{ github.ref_name }} - ${{ github.run_number }}" + +on: + push: + branches: + - master + - release/** + - github-mirror + paths: + - "**" + - "*" + - ".globalconfig" + - "!.github/ISSUE_TEMPLATE/**" + - "!.dependabot/config.yml" + - "!.pipelines/**" + - "!test/perf/**" + pull_request: + branches: + - master + - release/** + - github-mirror + - "*-feature" +# Path filters for PRs need to go into the changes job + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }} + cancel-in-progress: ${{ contains(github.ref, 'merge')}} + +env: + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + DOTNET_NOLOGO: 1 + FORCE_FEATURE: 'False' + FORCE_PACKAGE: 'False' + HOMEBREW_NO_ANALYTICS: 1 + NUGET_KEY: none + POWERSHELL_TELEMETRY_OPTOUT: 1 + __SuppressAnsiEscapeSequences: 1 + nugetMultiFeedWarnLevel: none + system_debug: 'false' + +jobs: + changes: + name: Change Detection + runs-on: ubuntu-latest + if: startsWith(github.repository_owner, 'azure') || github.repository_owner == 'PowerShell' + # 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Change Detection + id: filter + uses: "./.github/actions/infrastructure/path-filters" + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + ci_build: + name: Build PowerShell + runs-on: macos-15-large + needs: changes + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + - name: Build + uses: "./.github/actions/build/ci" + macos_test_unelevated_ci: + name: macos Unelevated CI + needs: + - ci_build + - changes + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} + runs-on: macos-15-large + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + - name: macOS Unelevated CI + uses: "./.github/actions/test/nix" + 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' || needs.changes.outputs.buildModuleChanged == 'true' }} + runs-on: macos-15-large + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + - name: macOS Elevated CI + uses: "./.github/actions/test/nix" + 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' || needs.changes.outputs.buildModuleChanged == 'true' }} + runs-on: macos-15-large + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + - name: macOS Unelevated Others + uses: "./.github/actions/test/nix" + 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' || needs.changes.outputs.buildModuleChanged == 'true' }} + runs-on: macos-15-large + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + - name: macOS Elevated Others + uses: "./.github/actions/test/nix" + with: + purpose: ElevatedPesterTests + tagSet: Others + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + xunit_tests: + name: xUnit Tests + needs: + - changes + 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 and testing + needs: + - changes + if: ${{ needs.changes.outputs.packagingChanged == 'true' }} + runs-on: + - macos-15-large + steps: + - name: checkout + 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() + 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: + - 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@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 02bb496a091..00000000000 --- a/.github/workflows/markdownLink.yml +++ /dev/null @@ -1,42 +0,0 @@ -on: - pull_request: - branches: - - master - -name: Check modified markdown files -permissions: - contents: read - -jobs: - markdown-link-check: - runs-on: ubuntu-latest - 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@b92721f792f381cedc002ecdbb9847a15ece5bb8 # v7.1.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 82734c829d6..00000000000 --- a/.github/workflows/processReminders.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: 'Process reminders' - -on: - schedule: - - cron: '*/15 * * * *' - workflow_dispatch: - -permissions: - contents: read - -jobs: - reminder: - 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@45201302ec0071cce809a483111bda4cdc7d10f2 # v1.0.15 diff --git a/.github/workflows/rebase.yml b/.github/workflows/rebase.yml deleted file mode 100644 index b6a45200eb6..00000000000 --- a/.github/workflows/rebase.yml +++ /dev/null @@ -1,39 +0,0 @@ -# This cannot rebase workflow changes into a PR -# It also only works if the GITHUB_TOKEN has permission to push to the branch -# see: https://github.com/cirrus-actions/rebase/issues/12#issuecomment-632594995 -on: - issue_comment: - types: [created] -name: Automatic Rebase -permissions: - contents: read - -jobs: - rebase: - permissions: - contents: write # for cirrus-actions/rebase to push code to rebase - pull-requests: write # for actions/github-script to create PR comment - name: Rebase - if: github.event.issue.pull_request != '' && contains(github.event.comment.body, '/rebase') - runs-on: ubuntu-latest - steps: - - name: Checkout the latest code - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - fetch-depth: 0 - - name: Post rebase started comment to pull request - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 - continue-on-error: true - with: - script: | - const backport_start_body = `Started rebase: https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${process.env.GITHUB_RUN_ID}`; - await github.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: backport_start_body - }); - - name: Automatic Rebase - uses: cirrus-actions/rebase@b87d48154a87a85666003575337e27b8cd65f691 # 1.8 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 1ac917d3af8..935c8ff93bb 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -20,6 +20,7 @@ permissions: read-all jobs: analysis: name: Scorecard analysis + if: github.repository_owner == 'PowerShell' runs-on: ubuntu-latest permissions: # Needed to upload the results to code-scanning dashboard. @@ -31,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@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 + uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 with: results_file: results.sarif results_format: sarif @@ -58,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@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: name: SARIF file path: results.sarif @@ -66,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 + 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 new file mode 100644 index 00000000000..8a57b8b9726 --- /dev/null +++ b/.github/workflows/windows-ci.yml @@ -0,0 +1,194 @@ +name: Windows-CI +on: + workflow_dispatch: + push: + branches: + - master + - release/** + - github-mirror + paths: + - "**" + - "*" + - ".globalconfig" + - "!.vsts-ci/misc-analysis.yml" + - "!.github/ISSUE_TEMPLATE/**" + - "!.dependabot/config.yml" + - "!test/perf/**" + - "!.pipelines/**" + pull_request: + branches: + - master + - release/** + - github-mirror + - "*-feature" + +# Path filters for PRs need to go into the changes job + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }} + cancel-in-progress: ${{ contains(github.ref, 'merge')}} + +permissions: + contents: read + +run-name: "${{ github.ref_name }} - ${{ github.run_number }}" + +env: + DOTNET_CLI_TELEMETRY_OPTOUT: 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 + runs-on: ubuntu-latest + if: startsWith(github.repository_owner, 'azure') || github.repository_owner == 'PowerShell' + # 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Change Detection + id: filter + uses: "./.github/actions/infrastructure/path-filters" + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + ci_build: + name: Build PowerShell + needs: changes + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} + runs-on: windows-latest + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + - name: Build + uses: "./.github/actions/build/ci" + windows_test_unelevated_ci: + name: Windows Unelevated CI + needs: + - ci_build + - changes + if: ${{ needs.changes.outputs.source == 'true' || needs.changes.outputs.buildModuleChanged == 'true' }} + runs-on: windows-latest + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + - name: Windows Unelevated CI + uses: "./.github/actions/test/windows" + 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' || needs.changes.outputs.buildModuleChanged == 'true' }} + runs-on: windows-latest + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + - name: Windows Elevated CI + uses: "./.github/actions/test/windows" + 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' || needs.changes.outputs.buildModuleChanged == 'true' }} + runs-on: windows-latest + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + - name: Windows Unelevated Others + uses: "./.github/actions/test/windows" + 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' || needs.changes.outputs.buildModuleChanged == 'true' }} + runs-on: windows-latest + steps: + - name: checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1000 + - name: Windows Elevated Others + uses: "./.github/actions/test/windows" + with: + purpose: ElevatedPesterTests + tagSet: Others + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + xunit_tests: + name: xUnit Tests + needs: + - changes + 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: + - 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@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 cb12a297984..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 @@ -111,3 +114,13 @@ msbuild.binlog # Ignore gzip files in the manpage folder assets/manpage/*.gz + +# Ignore files and folders generated by some gh cli extensions +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/RolloutSpec.json b/.pipelines/EV2Specs/ServiceGroupRoot/RolloutSpec.json new file mode 100644 index 00000000000..9ed971068cc --- /dev/null +++ b/.pipelines/EV2Specs/ServiceGroupRoot/RolloutSpec.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://ev2schema.azure.net/schemas/2020-01-01/rolloutSpecification.json", + "contentVersion": "1.0.0.0", + "rolloutMetadata": { + "serviceModelPath": "ServiceModel.json", + "ScopeBindingsPath": "ScopeBindings.json", + "name": "OneBranch-Demo-Container-Deployment", + "rolloutType": "Major", + "buildSource": { + "parameters": { + "versionFile": "buildver.txt" + } + }, + "Notification": { + "Email": { + "To": "default" + } + } + }, + "orchestratedSteps": [ + { + "name": "UploadLinuxContainer", + "targetType": "ServiceResource", + "targetName": "LinuxContainerUpload", + "actions": ["Shell/Run"] + } + ] +} diff --git a/.pipelines/EV2Specs/ServiceGroupRoot/ScopeBindings.json b/.pipelines/EV2Specs/ServiceGroupRoot/ScopeBindings.json new file mode 100644 index 00000000000..c3a98555867 --- /dev/null +++ b/.pipelines/EV2Specs/ServiceGroupRoot/ScopeBindings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://ev2schema.azure.net/schemas/2020-01-01/scopeBindings.json", + "contentVersion": "0.0.0.1", + "scopeBindings": [ + { + "scopeTagName": "Global", + "bindings": [ + { + "find": "__SUBSCRIPTION_ID__", + "replaceWith": "$azureSubscriptionId()" + }, + { + "find": "__RESOURCE_GROUP__", + "replaceWith": "$azureResourceGroup()" + }, + { + "find": "__BUILD_VERSION__", + "replaceWith": "$buildVersion()" + } + ] + } + ] +} diff --git a/.pipelines/EV2Specs/ServiceGroupRoot/ServiceModel.json b/.pipelines/EV2Specs/ServiceGroupRoot/ServiceModel.json new file mode 100644 index 00000000000..ce974fe69e5 --- /dev/null +++ b/.pipelines/EV2Specs/ServiceGroupRoot/ServiceModel.json @@ -0,0 +1,51 @@ +{ + "$schema": "https://ev2schema.azure.net/schemas/2020-01-01/serviceModel.json", + "contentVersion": "1.0.0.0", + "serviceMetadata": { + "serviceGroup": "OneBranch-PowerShellDocker", + "environment": "Test" + }, + "serviceResourceGroupDefinitions": [ + { + "name": "OneBranch-PowerShellDocker-RGDef", + "serviceResourceDefinitions": [ + { + "name": "OneBranch-PowerShellDocker.Shell-SRDef", + "composedOf": { + "extension": { + "shell": [ + { + "type": "Run", + "properties": { + "imageName": "adm-azurelinux-30-l", + "imageVersion": "v2" + } + } + ] + } + } + } + ] + } + ], + "serviceResourceGroups": [ + { + "azureResourceGroupName": "default", + "location": "West US 3", + "instanceOf": "OneBranch-PowerShellDocker-RGDef", + "azureSubscriptionId": "default", + "scopeTags": [ + { + "name": "Global" + } + ], + "serviceResources": [ + { + "Name": "LinuxContainerUpload", + "InstanceOf": "OneBranch-PowerShellDocker.Shell-SRDef", + "RolloutParametersPath": "UploadLinux.Rollout.json" + } + ] + } + ] +} diff --git a/.pipelines/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1 b/.pipelines/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1 new file mode 100644 index 00000000000..6797ff94575 --- /dev/null +++ b/.pipelines/EV2Specs/ServiceGroupRoot/Shell/Run/Run.ps1 @@ -0,0 +1,397 @@ +<# +This function gets info from pmc's derived list of all repositories and from mapping.json (which contains info on just the repositories powershell publishes packages to, their package formats, etc) +to create a list of repositories PowerShell cares about along with repository Ids, repository full Urls and associated package that will be published to it. +#> +function Get-MappedRepositoryIds { + param( + [Parameter(Mandatory)] + [hashtable] + $Mapping, + + [Parameter(Mandatory)] + $RepoList, + + # LTS is not consider a package in this context. + # LTS is just another package name. + [Parameter(Mandatory)] + [ValidateSet('stable', 'preview')] + $Channel + ) + + $mappedReposUsedByPwsh = @() + foreach ($package in $Mapping.Packages) + { + Write-Verbose "package: $package" + $packageChannel = $package.channel + if (!$packageChannel) { + $packageChannel = 'all' + } + + Write-Verbose "package channel: $packageChannel" + if ($packageChannel -eq 'all' -or $packageChannel -eq $Channel) + { + $repoIds = [System.Collections.Generic.List[string]]::new() + $packageFormat = $package.PackageFormat + Write-Verbose "package format: $packageFormat" -Verbose + $extension = [System.io.path]::GetExtension($packageFormat) + $packageType = $extension -replace '^\.' + + if ($package.distribution.count -gt 1) { + throw "Package $($package | out-string) has more than one Distribution." + } + + foreach ($distribution in $package.distribution) + { + $urlGlob = $package.url + switch ($packageType) + { + 'deb' { + $urlGlob = $urlGlob + '-apt' + } + 'rpm' { + $urlGlob = $urlGlob + '-yum' + } + default { + throw "Unknown package type: $packageType" + } + } + + Write-Verbose "---Finding repo id for: $urlGlob---" -Verbose + $repos = $RepoList | Where-Object { $_.name -eq $urlGlob } + + if ($repos.id) { + Write-Verbose "Found repo id: $($repos.id)" -Verbose + $repoIds.AddRange(([string[]]$repos.id)) + } + else { + throw "Could not find repo for $urlGlob" + } + + if ($repoIds.Count -gt 0) { + $mappedReposUsedByPwsh += ($package + @{ "RepoId" = $repoIds.ToArray() }) + } + } + } + } + + Write-Verbose -Verbose "mapped repos length: $($mappedReposUsedByPwsh.Length)" + return $mappedReposUsedByPwsh +} + +<# +This function creates package objects for the packages to be published, +with the package name (ie package name format resolve with channel based PackageName and pwsh version), repoId, distribution and package path. +#> +function Get-PackageObjects() { + param( + [Parameter(Mandatory)] + [psobject[]] + $RepoObjects, + + [Parameter(Mandatory)] + [string] + $ReleaseVersion, + + [Parameter(Mandatory)] + [string[]] + $PackageName + ) + + $packages = @() + + foreach ($pkg in $RepoObjects) + { + if ($pkg.RepoId.count -gt 1) { + throw "Package $($pkg.name) has more than one repo id." + } + + if ($pkg.Distribution.count -gt 1) { + throw "Package $($pkg.name) has more than one Distribution." + } + + $pkgRepo = $pkg.RepoId | Select-Object -First 1 + $pkgDistribution = $pkg.Distribution | Select-Object -First 1 + + foreach ($name in $PackageName) { + $pkgName = $pkg.PackageFormat.Replace('PACKAGE_NAME', $name).Replace('POWERSHELL_RELEASE', $ReleaseVersion) + + if ($pkgName.EndsWith('.rpm')) { + $pkgName = $pkgName.Replace($ReleaseVersion, $ReleaseVersion.Replace('-', '_')) + } + + $packagePath = "$pwshPackagesFolder/$pkgName" + $packagePathExists = Test-Path -Path $packagePath + if (!$packagePathExists) + { + throw "package path $packagePath does not exist" + } + + Write-Verbose "Creating package info object for package '$pkgName' for repo '$pkgRepo'" + $packages += @{ + PackagePath = $packagePath + PackageName = $pkgName + RepoId = $pkgRepo + Distribution = $pkgDistribution + } + + Write-Verbose -Verbose "package info obj: Name: $pkgName RepoId: $pkgRepo Distribution: $pkgDistribution PackagePath: $packagePath" + } + } + + Write-Verbose -Verbose "count of packages objects: $($packages.Length)" + return $packages +} + +<# +This function stages, uploads and publishes the powershell packages to their associated repositories in PMC. +#> +function Publish-PackageToPMC() { + param( + [Parameter(Mandatory)] + [pscustomobject[]] + $PackageObject, + + [Parameter(Mandatory)] + [string] + $ConfigPath, + + [Parameter(Mandatory)] + [bool] + $SkipPublish + ) + + # Don't fail outright when an error occurs, but instead pool them until + # after attempting to publish every package. That way we can choose to + # proceed for a partial failure. + $errorMessage = [System.Collections.Generic.List[string]]::new() + foreach ($finalPackage in $PackageObject) + { + Write-Verbose "---Staging package: $($finalPackage.PackageName)---" -Verbose + $packagePath = $finalPackage.PackagePath + $pkgRepo = $finalPackage.RepoId + + $extension = [System.io.path]::GetExtension($packagePath) + $packageType = $extension -replace '^\.' + Write-Verbose "packageType: $packageType" -Verbose + + $packageListJson = pmc --config $ConfigPath package $packageType list --file $packagePath + $list = $packageListJson | ConvertFrom-Json + + $packageId = @() + if ($list.count -ne 0) + { + Write-Verbose "Package '$packagePath' already exists, skipping upload" -Verbose + $packageId = $list.results.id | Select-Object -First 1 + } + else { + # PMC UPLOAD COMMAND + Write-Verbose -Verbose "Uploading package, config: '$ConfigPath' package: '$packagePath'" + $uploadResult = $null + try { + $uploadResult = pmc --config $ConfigPath package upload $packagePath --type $packageType + } + catch { + $errorMessage.Add("Uploading package $($finalPackage.PackageName) to $pkgRepo failed. See errors above for details.") + continue + } + + $packageId = ($uploadResult | ConvertFrom-Json).id + } + + Write-Verbose "Got package ID: '$packageId'" -Verbose + $distribution = $finalPackage.Distribution | select-object -First 1 + Write-Verbose "distribution: $distribution" -Verbose + + if (!$SkipPublish) + { + Write-Verbose "---Publishing package: $($finalPackage.PackageName) to $pkgRepo---" -Verbose + + if (($packageType -ne 'rpm') -and ($packageType -ne 'deb')) + { + throw "Unsupported package type: $packageType" + return 1 + } + else { + # PMC UPDATE COMMAND + $rawUpdateResponse = $null + try { + if ($packageType -eq 'rpm') { + $rawUpdateResponse = pmc --config $ConfigPath repo package update $pkgRepo --add-packages $packageId + } elseif ($packageType -eq 'deb') { + $rawUpdateResponse = pmc --config $ConfigPath repo package update $pkgRepo $distribution --add-packages $packageId + } + } + catch { + $errorMessage.Add("Invoking update for package $($finalPackage.PackageName) to $pkgRepo failed. See errors above for details.") + continue + } + + $state = ($rawUpdateResponse | ConvertFrom-Json).state + Write-Verbose -Verbose "update response state: $state" + if ($state -ne 'completed') { + $errorMessage.Add("Publishing package $($finalPackage.PackageName) to $pkgRepo failed: $rawUpdateResponse") + continue + } + } + + # PMC PUBLISH COMMAND + # The CLI outputs messages and JSON in the same stream, so we must sift through it for now + # This is planned to be fixed with a switch in a later release + Write-Verbose -Verbose ([pscustomobject]($package + @{ + PackageId = $packageId + })) + + # At this point, the changes are staged and will eventually be publish. + # Running publish, causes them to go live "immediately" + $rawPublishResponse = $null + try { + $rawPublishResponse = pmc --config $ConfigPath repo publish $pkgRepo + } + catch { + $errorMessage.Add("Invoking final publish for package $($finalPackage.PackageName) to $pkgRepo failed. See errors above for details.") + continue + } + + $publishState = ($rawPublishResponse | ConvertFrom-Json).state + Write-Verbose -Verbose "publish response state: $publishState" + if ($publishState -ne 'completed') { + $errorMessage.Add("Final publishing of package $($finalPackage.PackageName) to $pkgRepo failed: $rawPublishResponse") + continue + } + } else { + Write-Verbose -Verbose "Skipping Uploading package --config-file '$ConfigPath' package add '$packagePath' --repoID '$pkgRepo'" + } + } + + if ($errorMessage) { + throw $errorMessage -join [Environment]::NewLine + } +} + +if ($null -eq $env:MAPPING_FILE) +{ + Write-Verbose -Verbose "MAPPING_FILE variable didn't get passed correctly" + return 1 +} + +if ($null -eq $env:PWSH_PACKAGES_TARGZIP) +{ + Write-Verbose -Verbose "PWSH_PACKAGES_TARGZIP variable didn't get passed correctly" + return 1 +} + +if ($null -eq $env:PMC_METADATA) +{ + Write-Verbose -Verbose "PMC_METADATA variable didn't get passed correctly" + return 1 +} + +try { + Write-Verbose -Verbose "Downloading files" + Invoke-WebRequest -Uri $env:MAPPING_FILE -OutFile mapping.json + Invoke-WebRequest -Uri $env:PWSH_PACKAGES_TARGZIP -OutFile packages.tar.gz + Invoke-WebRequest -Uri $env:PMC_METADATA -OutFile pmcMetadata.json + + # create variables to those paths and test them + $mappingFilePath = Join-Path "/package/unarchive/" -ChildPath "mapping.json" + $mappingFilePathExists = Test-Path $mappingFilePath + if (!$mappingFilePathExists) + { + Write-Verbose -Verbose "mapping.json expected at $mappingFilePath does not exist" + return 1 + } + + $packagesTarPath = Join-Path -Path "/package/unarchive/" -ChildPath "packages.tar.gz" + $packagesTarPathExists = Test-Path $packagesTarPath + if (!$packagesTarPathExists) + { + Write-Verbose -Verbose "packages.tar.gz expected at $packagesTarPath does not exist" + return 1 + } + + # Extract files from 'packages.tar.gz' + Write-Verbose -Verbose "---Extracting files from packages.tar.gz---" + $pwshPackagesFolder = Join-Path -Path "/package/unarchive/" -ChildPath "packages" + New-Item -Path $pwshPackagesFolder -ItemType Directory + tar -xzvf $packagesTarPath -C $pwshPackagesFolder --force-local + Get-ChildItem $pwshPackagesFolder -Recurse + + $metadataFilePath = Join-Path -Path "/package/unarchive/" -ChildPath "pmcMetadata.json" + $metadataFilePathExists = Test-Path $metadataFilePath + if (!$metadataFilePathExists) + { + Write-Verbose -Verbose "pmcMetadata.json expected at $metadataFilePath does not exist" + return 1 + } + + # files in the extracted Run dir + $configPath = Join-Path '/package/unarchive/Run' -ChildPath 'settings.toml' + $configPathExists = Test-Path -Path $configPath + if (!$configPathExists) + { + Write-Verbose -Verbose "settings.toml expected at $configPath does not exist" + return 1 + } + + $pythonDlFolder = Join-Path '/package/unarchive/Run' -ChildPath 'python_dl' + $pyPathExists = Test-Path -Path $pythonDlFolder + if (!$pyPathExists) + { + Write-Verbose -Verbose "python_dl expected at $pythonDlFolder does not exist" + return 1 + } + + Write-Verbose -Verbose "Installing pmc-cli" + pip install --upgrade pip + pip --version --verbose + pip install /package/unarchive/Run/python_dl/*.whl + + # Get metadata + $channel = "" + $packageNames = @() + $metadataContent = Get-Content -Path $metadataFilePath | ConvertFrom-Json + $releaseVersion = $metadataContent.ReleaseTag.TrimStart('v') + $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') + } + else { + $channel = 'stable' + $packageNames = @('powershell') + } + + # Only add LTS package if not a rebuild branch + if ($lts -and -not $isRebuild) { + $packageNames += @('powershell-lts') + } + + Write-Verbose -Verbose "---Getting repository list---" + $rawResponse = pmc --config $configPath repo list --limit 800 + $response = $rawResponse | ConvertFrom-Json + $limit = $($response.limit) + $count = $($response.count) + Write-Verbose -Verbose "'pmc repo list' limit is: $limit and count is: $count" + $repoList = $response.results + + Write-Verbose -Verbose "---Getting package info---" + + + Write-Verbose "Reading mapping file from '$mappingFilePath'" -Verbose + $mapping = Get-Content -Raw -LiteralPath $mappingFilePath | ConvertFrom-Json -AsHashtable + $mappedReposUsedByPwsh = Get-MappedRepositoryIds -Mapping $mapping -RepoList $repoList -Channel $channel + $packageObjects = Get-PackageObjects -RepoObjects $mappedReposUsedByPwsh -PackageName $packageNames -ReleaseVersion $releaseVersion + Write-Verbose -Verbose "skip publish $skipPublish" + Publish-PackageToPMC -PackageObject $packageObjects -ConfigPath $configPath -SkipPublish $skipPublish +} +catch { + Write-Error -ErrorAction Stop $_.Exception.Message + return 1 +} + +return 0 diff --git a/.pipelines/EV2Specs/ServiceGroupRoot/UploadLinux.Rollout.json b/.pipelines/EV2Specs/ServiceGroupRoot/UploadLinux.Rollout.json new file mode 100644 index 00000000000..d7c75c2e216 --- /dev/null +++ b/.pipelines/EV2Specs/ServiceGroupRoot/UploadLinux.Rollout.json @@ -0,0 +1,54 @@ +{ + "$schema": "https://ev2schema.azure.net/schemas/2020-01-01/rolloutParameters.json", + "contentVersion": "1.0.0.0", + "shellExtensions": [ + { + "name": "Run", + "type": "Run", + "properties": { + "maxExecutionTime": "PT2H" + }, + "package": { + "reference": { + "path": "Shell/Run.tar" + } + }, + "launch": { + "command": [ + "/bin/bash", + "-c", + "pwsh ./Run/Run.ps1" + ], + "environmentVariables": [ + { + "name": "MAPPING_FILE", + "reference": + { + "path": "Parameters\\mapping.json" + } + }, + { + "name": "PWSH_PACKAGES_TARGZIP", + "reference": + { + "path": "Parameters\\packages.tar.gz" + } + }, + { + "name": "PMC_METADATA", + "reference": + { + "path": "Parameters\\pmcMetadata.json" + } + } + ], + "identity": { + "type": "userAssigned", + "userAssignedIdentities": [ + "default" + ] + } + } + } + ] +} diff --git a/.pipelines/EV2Specs/ServiceGroupRoot/buildVer.txt b/.pipelines/EV2Specs/ServiceGroupRoot/buildVer.txt new file mode 100644 index 00000000000..7dea76edb3d --- /dev/null +++ b/.pipelines/EV2Specs/ServiceGroupRoot/buildVer.txt @@ -0,0 +1 @@ +1.0.1 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 a050300b1f5..82f129a0a5e 100644 --- a/.pipelines/PowerShell-Coordinated_Packages-Official.yml +++ b/.pipelines/PowerShell-Coordinated_Packages-Official.yml @@ -1,4 +1,3 @@ -name: UnifiedPackageBuild-$(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 a39b4e866fc..8afce29ede7 100644 --- a/.pipelines/PowerShell-Packages-Official.yml +++ b/.pipelines/PowerShell-Packages-Official.yml @@ -24,45 +24,24 @@ parameters: # parameters are shown up in ADO UI in a build queue time displayName: Skip Signing type: string default: 'NO' + - 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/ltsc2019/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: - pipeline: CoOrdinatedBuildPipeline - source: 'PowerShell-Coordinated Packages-Official' + source: 'PowerShell-Coordinated Binaries-Official' trigger: branches: include: @@ -70,18 +49,25 @@ 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 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. @@ -98,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 new file mode 100644 index 00000000000..b5f57438925 --- /dev/null +++ b/.pipelines/PowerShell-Release-Official-Azure.yml @@ -0,0 +1,76 @@ +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)-prod-$(Build.BuildId) + +variables: + - template: templates/variables/PowerShell-Release-Azure-Variables.yml + parameters: + debug: ${{ parameters.debug }} + +resources: + repositories: + - repository: onebranchTemplates + type: git + name: OneBranch.Pipelines/GovernedTemplates + ref: refs/heads/main + + pipelines: + - pipeline: CoOrdinatedBuildPipeline + source: 'PowerShell-Coordinated Binaries-Official' + + - pipeline: PSPackagesOfficial + source: 'PowerShell-Packages-Official' + trigger: + branches: + include: + - master + - releases/* + +extends: + template: v2/OneBranch.Official.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 }} + + - template: /.pipelines/templates/release-publish-pmc.yml@self diff --git a/.pipelines/PowerShell-Release-Official.yml b/.pipelines/PowerShell-Release-Official.yml index f025c42f460..3528e6b1471 100644 --- a/.pipelines/PowerShell-Release-Official.yml +++ b/.pipelines/PowerShell-Release-Official.yml @@ -17,56 +17,41 @@ 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)-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 + - repository: PSInternalTools + type: git + name: PowerShellCore/Internal-PowerShellTeam-Tools + ref: refs/heads/master pipelines: - pipeline: CoOrdinatedBuildPipeline - source: 'PowerShell-Coordinated Packages-Official' + source: 'PowerShell-Coordinated Binaries-Official' - pipeline: PSPackagesOfficial source: 'PowerShell-Packages-Official' @@ -77,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: Netlock + Network: KS3 + incrementalSDLBinaryAnalysis: true cloudvault: enabled: false globalSdl: @@ -92,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: @@ -103,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: @@ -110,365 +94,9 @@ extends: tsaOptionsFile: .config\tsaoptions.json stages: - - stage: DownloadPackages - displayName: 'Download Packages' - dependsOn: [] - jobs: - - template: /.pipelines/templates/release-download-packages.yml@self - - - stage: msixbundle - displayName: 'Create MSIX Bundle' - dependsOn: [] - variables: - ob_release_environment: Test - 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' - packageNamePattern: '**/*linux-x64-fxdependent.tar.gz' - arm64: 'yes' - - - 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: - - DownloadPackages - - 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: 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/release-publish-pmc.yml@self - parameters: - skipPublish: ${{ parameters.SkipPMCPublish }} - - - stage: ReleaseDocker - dependsOn: PushGitTagAndMakeDraftPublic - displayName: 'Docker Release' - jobs: - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Start Docker Build - jobName: StartDockerBuild - instructions: | - Kick off Docker build - - - template: /.pipelines/templates/approvalJob.yml@self - parameters: - displayName: Start Docker Release - dependsOnJob: StartDockerBuild - 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 d694d00b816..13087fbbf65 100644 --- a/.pipelines/PowerShell-vPack-Official.yml +++ b/.pipelines/PowerShell-vPack-Official.yml @@ -1,5 +1,3 @@ -name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr) - trigger: none parameters: # parameters are shown up in ADO UI in a build queue time @@ -7,73 +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_$(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,119 +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_${{ parameters.architecture }}' - $vstsCommandString = "vso[task.setvariable variable=PackageArtifactName]$packageArtifactName" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - displayName: 'Set package artifact name' - - - download: PSPackagesOfficial - artifact: $(PackageArtifactName) - displayName: Download package - - - pwsh: 'Get-ChildItem $(System.ArtifactsDirectory)\* -recurse | Select-Object -ExpandProperty Name' - displayName: 'Capture Artifact Listing' - - - 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}') - { - $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 $(System.ArtifactsDirectory)\* -recurse -include *.zip, *.msi | 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 - Get-ChildItem -Path $(ob_outputDirectory)\* -Recurse - 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;**/*.dll;**/*.ps1;**/*.psm1' - 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 f469a49eef5..df5ebaac091 100644 --- a/.pipelines/apiscan-gen-notice.yml +++ b/.pipelines/apiscan-gen-notice.yml @@ -10,6 +10,9 @@ parameters: 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 @@ -20,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') }}: @@ -77,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 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 a5ce2b1c666..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 @@ -51,22 +52,13 @@ jobs: } displayName: 'Check suppress.json' - # Needed as per FAQ here: https://eng.ms/docs/products/onebranch/build/troubleshootingfaqs - - task: PowerShell@2 - displayName: 'Update Az.Storage Module' - inputs: - targetType: 'inline' - script: | - Get-PackageProvider -Name NuGet -ForceBootstrap - Install-Module -Name Az.Storage -Verbose -Force -AllowClobber - Uninstall-AzureRm -Verbose - - task: AzurePowerShell@5 displayName: Check if blob exists and delete if specified inputs: azureSubscription: az-blob-cicd-infra scriptType: inlineScript - azurePowerShellVersion: latestVersion + azurePowerShellVersion: LatestVersion + pwsh: true inline: | $containersToDelete = @('$(AzureVersion)', '$(AzureVersion)-private', '$(AzureVersion)-nuget', '$(AzureVersion)-gc') 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 b30d72f6a56..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,21 +48,18 @@ jobs: - template: ../SetVersionVariables.yml parameters: ReleaseTagVar: $(ReleaseTagVar) - CreateJson: yes - UseJson: no + CreateJson: no - template: ../insert-nuget-config-azfeed.yml parameters: repoRoot: '$(repoRoot)' - - pwsh: | - Import-Module .\build.psm1 -force - Start-PSBootstrap - workingDirectory: '$(repoRoot)' - retryCountOnTaskFailure: 2 - displayName: 'Bootstrap' - env: - __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) + - task: UseDotNet@2 + displayName: 'Use .NET Core sdk' + inputs: + useGlobalJson: true + packageType: 'sdk' + workingDirectory: $(Build.SourcesDirectory)" - pwsh: | Import-Module .\build.psm1 -force @@ -80,47 +71,6 @@ jobs: workingDirectory: '$(repoRoot)' retryCountOnTaskFailure: 2 - - pwsh: | - $modules = 'Az.Accounts', 'Az.Storage' - foreach($module in $modules) { - if(!(get-module $module -listavailable)) { - Write-Verbose "installing $module..." -verbose - Install-Module $module -force -AllowClobber - } else { - Write-Verbose "$module already installed." -verbose - } - } - displayName: Install PowerShell modules - workingDirectory: '$(repoRoot)' - - - 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') @@ -139,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 0c1282ea8ce..aec44b9b8f6 100644 --- a/.pipelines/templates/compliance/generateNotice.yml +++ b/.pipelines/templates/compliance/generateNotice.yml @@ -8,8 +8,6 @@ parameters: jobs: - job: generateNotice variables: - - name: runCodesignValidationInjection - value : false - name: NugetSecurityAnalysisWarningLevel value: none - name: ob_outputDirectory @@ -55,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 - + sourceScanPath: '$(repoRoot)\tools\cgmanifest\tpn' - task: msospo.ospo-extension.8d7f9abb-6896-461d-9e25-4f74ed65ddb2.notice@0 displayName: 'NOTICE File Generator' @@ -71,7 +64,6 @@ jobs: # this isn't working # additionaldata: $(Build.SourcesDirectory)\assets\additionalAttributions.txt - - pwsh: | Get-Content -Raw -Path $(repoRoot)\assets\additionalAttributions.txt | Out-File '$(ob_outputDirectory)\ThirdPartyNotices.txt' -Encoding utf8NoBOM -Force -Append Get-Content -Raw -Path $(repoRoot)\assets\additionalAttributions.txt @@ -83,40 +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 - - - powershell: | - $modules = 'Az.Accounts', 'Az.Storage' - foreach($module in $modules) { - if(!(get-module $module -listavailable)) { - Write-Verbose "installing $module..." -verbose - Install-Module $module -force -AllowClobber - } else { - Write-Verbose "$module already installed." -verbose - #Update-Module $module -verbose - } - } - displayName: Install PowerShell modules - - - powershell: | - if(Get-Command -Name Uninstall-AzureRm -ErrorAction Ignore){ - Write-Verbose "running Uninstall-AzureRm" -verbose - Uninstall-AzureRm - } else { - Write-Verbose "Uninstall-AzureRm not present" -verbose - } - displayName: Uninstall Uninstall-AzureRm - continueOnError: true - - 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 46aea977e73..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 -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 7daa73f3a30..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,6 +62,8 @@ jobs: AnalyzeInPipeline: false Language: csharp + - template: /.pipelines/templates/install-dotnet.yml@self + - pwsh: | $runtime = $env:RUNTIME @@ -77,7 +77,6 @@ jobs: Import-Module -Name $(PowerShellRoot)/build.psm1 -Force $buildWithSymbolsPath = New-Item -ItemType Directory -Path $(Pipeline.Workspace)/Symbols_$(Runtime) -Force - Start-PSBootstrap $null = New-Item -ItemType Directory -Path $buildWithSymbolsPath -Force -Verbose $ReleaseTagParam = @{} @@ -138,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 @@ -195,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 f1b9c9b72ef..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 -Package + 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 4f9604ea100..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,9 +37,12 @@ jobs: sudo chown $env:USER "$(Agent.TempDirectory)/PowerShell" displayName: 'Create $(Agent.TempDirectory)/PowerShell' + ## 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 - Start-PSBootstrap -Package + Start-PSBootstrap -Scenario Package displayName: 'Bootstrap VM' env: __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) @@ -68,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)" @@ -141,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 e0d22744c30..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,12 @@ jobs: parameters: repoRoot: $(PowerShellRoot) - - task: NuGetToolInstaller@1 - displayName: 'Install NuGet.exe' + - template: /.pipelines/templates/install-dotnet.yml@self - pwsh: | Set-Location -Path '$(PowerShellRoot)' Import-Module "$(PowerShellRoot)/build.psm1" -Force - Start-PSBootstrap -Verbose - $sharedModules = @('Microsoft.PowerShell.Commands.Management', 'Microsoft.PowerShell.Commands.Utility', 'Microsoft.PowerShell.ConsoleHost', @@ -120,32 +115,32 @@ jobs: $refAssemblyFolder = Join-Path '$(System.ArtifactsDirectory)' 'RefAssembly' $null = New-Item -Path $refAssemblyFolder -Force -Verbose -Type Directory - Start-PSBuild -Clean -Runtime linux-x64 -Configuration Release + 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 } } - Start-PSBuild -Clean -Runtime win7-x64 -Configuration Release + 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 @@ -214,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' @@ -274,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 342c2f4a225..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,16 +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 - - 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)' @@ -105,7 +100,9 @@ jobs: Import-Module "$repoRoot\build.psm1" Import-Module "$repoRoot\tools\packaging" - Start-PSBootstrap -Package + Start-PSBootstrap -Scenario Both + + Find-Dotnet $signedFilesPath, $psoptionsFilePath = if ($env:RUNTIME -eq 'minsize') { "$(Pipeline.Workspace)\CoOrdinatedBuildPipeline\drop_windows_build_windows_x64_${runtime}\$signedFolder" @@ -130,13 +127,29 @@ 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" } - Start-PSBootstrap -Package + Start-PSBootstrap -Scenario Package $WindowsRuntime = switch ($runtime) { 'x64' { 'win7-x64' } @@ -148,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' } @@ -164,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' } @@ -261,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 f11a0839e47..758298202a1 100644 --- a/.pipelines/templates/release-MakeBlobPublic.yml +++ b/.pipelines/templates/release-MakeBlobPublic.yml @@ -45,24 +45,12 @@ jobs: - template: /.pipelines/templates/SetVersionVariables.yml@self parameters: ReleaseTagVar: $(ReleaseTagVar) - CreateJson: yes - UseJson: no + CreateJson: no - pwsh: | Get-ChildItem Env: displayName: 'Capture Environment Variables' - - pwsh: | - $azureRmModule = Get-InstalledModule AzureRM -ErrorAction SilentlyContinue -Verbose - if ($azureRmModule) { - Write-Host 'AzureRM module exists. Removing it' - Uninstall-AzureRm - Write-Host 'AzureRM module removed' - } - - Install-Module -Name Az.Storage -Force -AllowClobber -Scope CurrentUser -Verbose - displayName: Remove AzRM modules - - task: AzurePowerShell@5 displayName: Copy blobs to PSInfra storage inputs: @@ -143,24 +131,12 @@ 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 displayName: 'Capture Environment Variables' - - pwsh: | - $azureRmModule = Get-InstalledModule AzureRM -ErrorAction SilentlyContinue -Verbose - if ($azureRmModule) { - Write-Host 'AzureRM module exists. Removing it' - Uninstall-AzureRm - Write-Host 'AzureRM module removed' - } - - Install-Module -Name Az.Storage -Force -AllowClobber -Scope CurrentUser -Verbose - displayName: Remove AzRM modules - - task: AzurePowerShell@5 displayName: Copy blobs to PSInfra storage inputs: diff --git a/.pipelines/templates/release-SetReleaseTagandContainerName.yml b/.pipelines/templates/release-SetReleaseTagandContainerName.yml index 7e88624b45c..d40551353d2 100644 --- a/.pipelines/templates/release-SetReleaseTagandContainerName.yml +++ b/.pipelines/templates/release-SetReleaseTagandContainerName.yml @@ -1,3 +1,7 @@ +parameters: +- name: restorePhase + default: false + steps: - pwsh: | $variable = 'releaseTag' @@ -8,19 +12,25 @@ steps: } $releaseTag = $Branch -replace '^.*((release|rebuild)/)' - $vstsCommandString = "vso[task.setvariable variable=$Variable]$releaseTag" + $vstsCommandString = "vso[task.setvariable variable=$Variable;isOutput=true]$releaseTag" Write-Verbose -Message "setting $Variable to $releaseTag" -Verbose Write-Host -Object "##$vstsCommandString" + name: OutputReleaseTag displayName: Set Release Tag + env: + ob_restore_phase: ${{ parameters.restorePhase }} - pwsh: | - $azureVersion = '$(ReleaseTag)'.ToLowerInvariant() -replace '\.', '-' - $vstsCommandString = "vso[task.setvariable variable=AzureVersion]$azureVersion" + $azureVersion = '$(OutputReleaseTag.ReleaseTag)'.ToLowerInvariant() -replace '\.', '-' + $vstsCommandString = "vso[task.setvariable variable=AzureVersion;isOutput=true]$azureVersion" Write-Host "sending " + $vstsCommandString Write-Host "##$vstsCommandString" - $version = '$(ReleaseTag)'.ToLowerInvariant().Substring(1) - $vstsCommandString = "vso[task.setvariable variable=Version]$version" + $version = '$(OutputReleaseTag.ReleaseTag)'.ToLowerInvariant().Substring(1) + $vstsCommandString = "vso[task.setvariable variable=Version;isOutput=true]$version" Write-Host ("sending " + $vstsCommandString) Write-Host "##$vstsCommandString" + name: OutputVersion displayName: Set container name + env: + ob_restore_phase: ${{ parameters.restorePhase }} diff --git a/.pipelines/templates/release-SetTagAndChangelog.yml b/.pipelines/templates/release-SetTagAndChangelog.yml new file mode 100644 index 00000000000..b33e652b3c7 --- /dev/null +++ b/.pipelines/templates/release-SetTagAndChangelog.yml @@ -0,0 +1,51 @@ +jobs: +- job: setTagAndChangelog + displayName: Set Tag and Upload Changelog + condition: succeeded() + pool: + type: windows + variables: + - group: 'mscodehub-code-read-akv' + - name: ob_outputDirectory + value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' + - name: ob_sdl_credscan_suppressionsFile + value: $(Build.SourcesDirectory)\PowerShell\.config\suppress.json + - name: ob_sdl_tsa_configFile + value: $(Build.SourcesDirectory)\PowerShell\.config\tsaoptions.json + steps: + - template: release-SetReleaseTagandContainerName.yml@self + + - checkout: self + clean: true + env: + ob_restore_phase: true + + - pwsh: | + Write-Verbose -Verbose "Release Tag: $(OutputReleaseTag.releaseTag)" + $releaseVersion = '$(OutputReleaseTag.releaseTag)' -replace '^v','' + Write-Verbose -Verbose "Release Version: $releaseVersion" + $semanticVersion = [System.Management.Automation.SemanticVersion]$releaseVersion + + $isPreview = $semanticVersion.PreReleaseLabel -ne $null + + $fileName = if ($isPreview) { + "preview.md" + } + else { + $semanticVersion.Major.ToString() + "." + $semanticVersion.Minor.ToString() + ".md" + } + + $filePath = "$(Build.SourcesDirectory)/PowerShell/CHANGELOG/$fileName" + Write-Verbose -Verbose "Selected Log file: $filePath" + + if (-not (Test-Path -Path $filePath)) { + Write-Error "Changelog file not found: $filePath" + exit 1 + } + + Write-Verbose -Verbose "Creating output directory for CHANGELOG: $(ob_outputDirectory)/CHANGELOG" + 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 448a46c1194..00000000000 --- a/.pipelines/templates/release-create-msix.yml +++ /dev/null @@ -1,118 +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' - - - 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 - Invoke-RestMethod -Uri '$(makeappUrl)' -OutFile "$toolsDir\makeappx.zip" - Expand-Archive "$toolsDir\makeappx.zip" -DestinationPath "$toolsDir\makeappx" -Force - $exePath = "$toolsDir\makeappx\makeappx.exe" - - Write-Verbose -Verbose 'makeappx was installed:' - Get-ChildItem -Path $toolsDir -Recurse - } - - $vstsCommandString = "vso[task.setvariable variable=MakeAppxPath]$exePath" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - displayName: Install 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 - - - pwsh: | - $azureRmModule = Get-InstalledModule AzureRM -ErrorAction SilentlyContinue -Verbose - if ($azureRmModule) { - Write-Host 'AzureRM module exists. Removing it' - Uninstall-AzureRm - Write-Host 'AzureRM module removed' - } - - Install-Module -Name Az.Storage -Force -AllowClobber -Scope CurrentUser -Verbose - - displayName: Remove AzRM modules and install Az.Storage - - - task: AzurePowerShell@5 - displayName: Upload msix to blob - inputs: - azureSubscription: az-blob-cicd-infra - scriptType: inlineScript - azurePowerShellVersion: LatestVersion - pwsh: true - inline: | - $containerName = '$(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 42db2b20b73..00000000000 --- a/.pipelines/templates/release-githubtasks.yml +++ /dev/null @@ -1,116 +0,0 @@ -jobs: -- job: GithubReleaseDraft - displayName: Create GitHub Release Draft - condition: succeeded() - pool: - type: release - os: windows - templateContext: - inputs: - - input: pipelineArtifact - artifactName: drop_DownloadPackages_upload_packages - variables: - - template: ./variable/release-shared.yml@self - - steps: - - task: PowerShell@2 - inputs: - targetType: inline - script: | - Get-ChildItem Env: | Out-String -Stream | write-Verbose -Verbose - displayName: 'Capture Environment Variables' - - - template: release-install-pwsh.yml - - - template: release-checkout-pwsh-repo.yml - - - template: release-SetReleaseTagAndContainerName.yml - - - task: PowerShell@2 - inputs: - targetType: inline - pwsh: true - script: | - git clone --depth 1 https://$(mscodehubCodeReadPat)@mscodehub.visualstudio.com/PowerShellCore/_git/Internal-PowerShellTeam-Tools '$(Pipeline.Workspace)/tools' - displayName: Clone Internal-Tools repository - - - 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: | - $releaseVersion = '$(ReleaseTag)' -replace '^v','' - $vstsCommandString = "vso[task.setvariable variable=ReleaseVersion]$releaseVersion" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - displayName: 'Set release version' - - - 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)/tools/Scripts/GitHubRelease.psm1' - $releaseVersion = '$(ReleaseTag)' -replace '^v','' - $semanticVersion = [System.Management.Automation.SemanticVersion]$releaseVersion - - $isPreview = $semanticVersion.PreReleaseLabel -ne $null - - $fileName = if ($isPreview) { - "preview.md" - } - else { - $semanticVersion.Major.ToString() + "." + $semanticVersion.Minor.ToString() + ".md" - } - - $filePath = "$(Pipeline.Workspace)/PowerShell/CHANGELOG/$fileName" - Write-Verbose -Verbose "Selected Log file: $filePath" - - if (-not (Test-Path $filePath)) { - throw "$filePath not found" - } - - $changelog = Get-Content -Path $filePath - - $startPattern = "^## \[" + ([regex]::Escape($releaseVersion)) + "\]" - $endPattern = "^## \[{0}\.{1}\.{2}*" -f $semanticVersion.Major, $semanticVersion.Minor, $semanticVersion.Patch - - $clContent = $changelog | ForEach-Object { - if ($_ -match $startPattern) { $outputLine = $true } - elseif ($_ -match $endPattern) { $outputLine = $false } - if ($outputLine) { $_} - } | 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 new file mode 100644 index 00000000000..3ad716a3af4 --- /dev/null +++ b/.pipelines/templates/release-prep-for-ev2.yml @@ -0,0 +1,231 @@ +parameters: +- name: skipPublish + type: boolean + default: false + +stages: +- stage: PrepForEV2 + displayName: 'Copy and prep all files needed for EV2 stage' + jobs: + - job: CopyEV2FilesToArtifact + 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' + - name: repoRoot + value: '$(Build.SourcesDirectory)/PowerShell' + - name: ev2ServiceGroupRootFolder + value: '$(Build.SourcesDirectory)/PowerShell/.pipelines/EV2Specs/ServiceGroupRoot' + - name: ev2ParametersFolder + value: '$(Build.SourcesDirectory)/PowerShell/.pipelines/EV2Specs/ServiceGroupRoot/Parameters' + - group: 'mscodehub-code-read-akv' + - group: 'packages.microsoft.com' + - name: ob_sdl_credscan_suppressionsFile + 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 + env: + ob_restore_phase: true + + - template: release-SetReleaseTagandContainerName.yml + parameters: + restorePhase: true + + - pwsh: | + $packageVersion = '$(OutputReleaseTag.ReleaseTag)'.ToLowerInvariant() -replace '^v','' + $vstsCommandString = "vso[task.setvariable variable=packageVersion]$packageVersion" + Write-Host "sending " + $vstsCommandString + Write-Host "##$vstsCommandString" + displayName: Set Package version + env: + ob_restore_phase: true + + - pwsh: | + $branch = 'mirror-target' + $gitArgs = "clone", + "--verbose", + "--branch", + "$branch", + "https://$(mscodehubCodeReadPat)@mscodehub.visualstudio.com/PowerShellCore/_git/Internal-PowerShellTeam-Tools", + '$(Pipeline.Workspace)/tools' + $gitArgs | Write-Verbose -Verbose + git $gitArgs + displayName: Clone Internal-PowerShellTeam-Tools from MSCodeHub + env: + ob_restore_phase: true + + - pwsh: | + Get-ChildItem Env: | Out-String -Stream | write-Verbose -Verbose + displayName: 'Capture Environment Variables' + env: + ob_restore_phase: true + + - pwsh: | + Get-ChildItem '$(Build.SourcesDirectory)' + displayName: 'Capture BuildDirectory' + env: + ob_restore_phase: true + + - pwsh: | + Get-ChildItem '$(Pipeline.Workspace)' -Recurse | Out-String -Stream | write-Verbose -Verbose + displayName: 'Capture Workspace' + env: + ob_restore_phase: true + + - pwsh: | + New-Item -Path '$(ev2ParametersFolder)' -ItemType Directory + displayName: 'Create Parameters folder under EV2Specs folder' + env: + ob_restore_phase: true + + - task: PipAuthenticate@1 + inputs: + artifactFeeds: 'PowerShellCore/PowerShellCore_PublicPackages' + displayName: 'Pip Authenticate' + env: + ob_restore_phase: true + + - pwsh: | + python3 -m pip install --upgrade pip + pip --version --verbose + + Write-Verbose -Verbose "Download pmc-cli to folder without installing it" + $pythonDlFolderPath = Join-Path '$(ev2ServiceGroupRootFolder)/Shell/Run' -ChildPath "python_dl" + pip download -d $pythonDlFolderPath pmc-cli --platform=manylinux_2_17_x86_64 --only-binary=:all: --verbose + displayName: 'Download pmc-cli package' + env: + ob_restore_phase: true + + - pwsh: | + Write-Verbose -Verbose "Copy ESRP signed .deb and .rpm packages" + # 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 -File | Where-Object { $_.Extension -in '.deb', '.rpm' } + foreach ($file in $packageFiles) + { + Write-Verbose -Verbose "copying file: $($file.FullName)" + Copy-Item -Path $($file.FullName) -Destination $packagesFolder -Verbose + } + + $packagesTarGzDestination = Join-Path -Path '$(ev2ParametersFolder)' -ChildPath 'packages.tar.gz' + tar -czvf $packagesTarGzDestination -C $packagesFolder . + displayName: 'Copy signed .deb and .rpm packages to .tar.gz to pass as a file var to shell extension' + env: + ob_restore_phase: true + + - pwsh: | + $pathToPMCMetadataFile = Join-Path -Path '$(ev2ParametersFolder)' -ChildPath 'pmcMetadata.json' + + $metadata = Get-Content -Path "$(repoRoot)/tools/metadata.json" -Raw | ConvertFrom-Json + $metadataHash = @{} + $skipPublishValue = '${{ parameters.skipPublish }}' + $metadataHash["ReleaseTag"] = '$(OutputReleaseTag.ReleaseTag)' + $metadataHash["LTS"] = $metadata.LTSRelease.PublishToChannels + $metadataHash["ForProduction"] = $true + $metadataHash["SkipPublish"] = [System.Convert]::ToBoolean($skipPublishValue) + + $metadataHash | ConvertTo-Json | Out-File $pathToPMCMetadataFile + + $mappingFilePath = Join-Path -Path '$(repoRoot)/tools/packages.microsoft.com' -ChildPath 'mapping.json' + $mappingFilePathExists = Test-Path $mappingFilePath + $mappingFileEV2Path = Join-Path -Path '$(ev2ParametersFolder)' -ChildPath "mapping.json" + Write-Verbose -Verbose "Copy mapping.json file at: $mappingFilePath which exists: $mappingFilePathExists to: $mappingFileEV2Path" + Copy-Item -Path $mappingFilePath -Destination $mappingFileEV2Path + displayName: 'Create pmcScriptMetadata.json and mapping.json file' + env: + ob_restore_phase: true + + - pwsh: | + $pathToJsonFile = Join-Path -Path '$(ev2ServiceGroupRootFolder)' -ChildPath 'RolloutSpec.json' + $content = Get-Content -Path $pathToJsonFile | ConvertFrom-Json + $content.RolloutMetadata.Notification.Email.To = '$(PmcEV2SupportEmail)' + Remove-Item -Path $pathToJsonFile + $content | ConvertTo-Json -Depth 4 | Out-File $pathToJsonFile + displayName: 'Replace values in RolloutSpecPath.json' + env: + ob_restore_phase: true + + - pwsh: | + $pathToJsonFile = Join-Path -Path '$(ev2ServiceGroupRootFolder)' -ChildPath 'UploadLinux.Rollout.json' + $content = Get-Content -Path $pathToJsonFile | ConvertFrom-Json + + $identityString = "/subscriptions/$(PmcSubscription)/resourcegroups/$(PmcResourceGroup)/providers/Microsoft.ManagedIdentity/userAssignedIdentities/$(PmcMIName)" + $content.shellExtensions.launch.identity.userAssignedIdentities[0] = $identityString + + Remove-Item -Path $pathToJsonFile + $content | ConvertTo-Json -Depth 6 | Out-File $pathToJsonFile + displayName: 'Replace values in UploadLinux.Rollout.json file' + env: + ob_restore_phase: true + + - pwsh: | + $pathToJsonFile = Join-Path -Path '$(ev2ServiceGroupRootFolder)' -ChildPath 'ServiceModel.json' + $content = Get-Content -Path $pathToJsonFile | ConvertFrom-Json + $content.ServiceResourceGroups[0].AzureResourceGroupName = '$(PmcResourceGroup)' + $content.ServiceResourceGroups[0].AzureSubscriptionId = '$(PmcSubscription)' + + Remove-Item -Path $pathToJsonFile + $content | ConvertTo-Json -Depth 9 | Out-File $pathToJsonFile + displayName: 'Replace values in ServiceModel.json' + env: + ob_restore_phase: true + + - pwsh: | + $settingFilePath = Join-Path '$(ev2ServiceGroupRootFolder)/Shell/Run' -ChildPath 'settings.toml' + New-Item -Path $settingFilePath -ItemType File + $pmcMIClientID = '$(PmcMIClientID)' + $pmcEndpoint = '$(PmcEndpointUrl)' + + Add-Content -Path $settingFilePath -Value "[default]" + Add-Content -Path $settingFilePath -Value "base_url = `"$pmcEndpoint`"" + Add-Content -Path $settingFilePath -Value "auth_type = `"msi`"" + Add-Content -Path $settingFilePath -Value "client_id = `"$pmcMIClientID`"" + displayName: 'Create settings.toml file with MI clientId populated' + env: + ob_restore_phase: true + + - task: onebranch.pipeline.signing@1 + inputs: + command: 'sign' + signing_profile: external_distribution + 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' + $pathToRunTarFile = Join-Path $srcPath -ChildPath "Run.tar" + tar -cvf $pathToRunTarFile -C $srcPath ./Run + displayName: 'Create archive for the shell extension' + + - task: CopyFiles@2 + inputs: + SourceFolder: '$(repoRoot)/.pipelines' + Contents: 'EV2Specs/**' + TargetFolder: $(ob_outputDirectory) diff --git a/.pipelines/templates/release-publish-nuget.yml b/.pipelines/templates/release-publish-nuget.yml deleted file mode 100644 index e4810e63529..00000000000 --- a/.pipelines/templates/release-publish-nuget.yml +++ /dev/null @@ -1,57 +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_DownloadPackages_upload_packages - - variables: - - template: ./variable/release-shared.yml@self - - steps: - - template: release-install-pwsh.yml - - - template: release-checkout-pwsh-repo.yml - - - template: release-SetReleaseTagAndContainerName.yml - - - pwsh: | - 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 27311611e61..dc7fc8534e3 100644 --- a/.pipelines/templates/release-publish-pmc.yml +++ b/.pipelines/templates/release-publish-pmc.yml @@ -1,90 +1,56 @@ parameters: - - name: skipPublish - default: false - type: boolean - -jobs: -- job: PMCPublish - displayName: Publish to PMC - condition: succeeded() - pool: - type: linux - isCustom: true - name: PowerShell1ES - demands: - - ImageOverride -equals PSMMSUbuntu20.04-Secure +- 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: ${{ parameters.stagePrefix }}_Release + displayName: 'Deploy packages to PMC with EV2' + dependsOn: + - PrepForEV2 variables: - - name: runCodesignValidationInjection - value: false - - name: NugetSecurityAnalysisWarningLevel - value: none - - name: DOTNET_SKIP_FIRST_TIME_EXPERIENCE - value: 1 - - group: 'mscodehub-code-read-akv' - - group: 'packages.microsoft.com' - - name: ob_outputDirectory - value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' - - name: ob_sdl_codeSignValidation_enabled - value: false - - name: ob_sdl_binskim_enabled - value: false - - 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 ## the global setting on lfs didn't work - lfs: false - - - template: release-SetReleaseTagAndContainerName.yml - - - pwsh: | - $packageVersion = '$(ReleaseTag)'.ToLowerInvariant() -replace '^v','' - $vstsCommandString = "vso[task.setvariable variable=packageVersion]$packageVersion" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - displayName: Set Package version - - - pwsh: | - $branch = 'mirror-target' - $gitArgs = "clone", - "--verbose", - "--branch", - "$branch", - "https://$(mscodehubCodeReadPat)@mscodehub.visualstudio.com/PowerShellCore/_git/Internal-PowerShellTeam-Tools", - '$(Pipeline.Workspace)/tools' - $gitArgs | Write-Verbose -Verbose - git $gitArgs - displayName: Clone Internal-PowerShellTeam-Tools from MSCodeHub - - - task: PipAuthenticate@1 - inputs: - artifactFeeds: 'pmc' - pythonDownloadServiceConnections: pmcDownload - - - pwsh: | - pip install pmc-cli==1.12.0 - - $newPath = (resolve-path '~/.local/bin').providerpath - $vstsCommandString = "vso[task.setvariable variable=PATH]${env:PATH}:$newPath" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - displayName: Install pmc cli - - - pwsh: | - $metadata = Get-Content -Path "$(Build.SourcesDirectory)/tools/metadata.json" -Raw | ConvertFrom-Json - $params = @{ - ReleaseTag = "$(ReleaseTag)" - AadClientId = "$(PmcCliClientID)" - BlobFolderName = "$(ReleaseTag)" - LTS = $metadata.LTSRelease.Latest - ForProduction = $true - SkipPublish = $${{ parameters.skipPublish }} - MappingFilePath = '$(System.DefaultWorkingDirectory)/tools/packages.microsoft.com/mapping.json' - } - - $params | Out-String -width 9999 -Stream | write-Verbose -Verbose - - & '$(Pipeline.Workspace)/tools/packages.microsoft.com-v4/releaseLinuxPackages.ps1' @params - displayName: Run release script + - name: ob_release_environment + value: ${{ parameters.releaseEnvironment }} + - name: repoRoot + value: $(Build.SourcesDirectory) + jobs: + - job: ${{ parameters.stagePrefix }}_ReleaseJob + displayName: Publish to PMC + pool: + type: release + templateContext: + inputs: + - input: pipelineArtifact + artifactName: drop_PrepForEV2_CopyEv2FilesToArtifact + + 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 27af6c87b64..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,54 +71,51 @@ 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 - - pwsh: | - $azureRmModule = Get-InstalledModule AzureRM -ErrorAction SilentlyContinue -Verbose - if ($azureRmModule) { - Write-Host 'AzureRM module exists. Removing it' - Uninstall-AzureRm - Write-Host 'AzureRM module removed' - } - - Install-Module -Name Az.Storage -Force -AllowClobber -Scope CurrentUser -Verbose - displayName: Remove AzRM modules - - task: AzurePowerShell@5 displayName: Upload buildjson to blob inputs: @@ -129,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 344db621632..3f4f9a3bb6c 100644 --- a/.pipelines/templates/release-validate-fxdpackages.yml +++ b/.pipelines/templates/release-validate-fxdpackages.yml @@ -1,10 +1,25 @@ parameters: - jobName: "" - displayName: "" - jobtype: "" - artifactName: "" - packageNamePattern: "" - arm64: "no" + - name: jobName + type: string + default: "" + - name: displayName + type: string + default: "" + - name: jobtype + type: string + default: "" + - name: artifactName + type: string + default: "" + - name: packageNamePattern + type: string + default: "" + - name: arm64 + type: string + default: "no" + - name: enableCredScan + type: boolean + default: true jobs: - job: ${{ parameters.jobName }} @@ -19,6 +34,8 @@ jobs: value: $(Build.SourcesDirectory)\PowerShell\.config\suppress.json - name: ob_sdl_tsa_configFile value: $(Build.SourcesDirectory)\PowerShell\.config\tsaoptions.json + - name: ob_sdl_credscan_enabled + value: ${{ parameters.enableCredScan }} pool: type: ${{ parameters.jobtype }} @@ -44,38 +61,7 @@ jobs: Get-ChildItem "$(Pipeline.Workspace)/PSPackagesOfficial/$artifactName" -Recurse displayName: 'Capture Downloaded Artifacts' - - pwsh: | - $repoRoot = "$(Build.SourcesDirectory)/PowerShell" - $dotnetMetadataPath = "$repoRoot/DotnetRuntimeMetadata.json" - $dotnetMetadataJson = Get-Content $dotnetMetadataPath -Raw | ConvertFrom-Json - - # Channel is like: $Channel = "5.0.1xx-preview2" - $Channel = $dotnetMetadataJson.sdk.channel - - $sdkVersion = (Get-Content "$repoRoot/global.json" -Raw | ConvertFrom-Json).sdk.version - Import-Module "$repoRoot/build.psm1" -Force - - Find-Dotnet - - if(-not (Get-PackageSource -Name 'dotnet' -ErrorAction SilentlyContinue)) - { - $nugetFeed = ([xml](Get-Content $repoRoot/nuget.config -Raw)).Configuration.packagesources.add | Where-Object { $_.Key -eq 'dotnet' } | Select-Object -ExpandProperty Value - if ($nugetFeed) { - Register-PackageSource -Name 'dotnet' -Location $nugetFeed -ProviderName NuGet - Write-Verbose -Message "Register new package source 'dotnet'" -verbose - } - } - - ## Install latest version from the channel - - #Install-Dotnet -Channel "$Channel" -Version $sdkVersion - Start-PSBootstrap - - Write-Verbose -Message "Installing .NET SDK completed." -Verbose - - displayName: Install .NET - env: - __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) + - template: /.pipelines/templates/install-dotnet.yml@self - pwsh: | $artifactName = '$(artifactName)' @@ -107,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 3dc275adee1..8c2031d5cc9 100644 --- a/.pipelines/templates/release-validate-globaltools.yml +++ b/.pipelines/templates/release-validate-globaltools.yml @@ -38,44 +38,13 @@ jobs: Get-ChildItem "$(Pipeline.Workspace)/PSPackagesOfficial/drop_nupkg_build_nupkg" -Recurse displayName: 'Capture Downloaded Artifacts' - - pwsh: | - $repoRoot = "$(Build.SourcesDirectory)/PowerShell" - $dotnetMetadataPath = "$repoRoot/DotnetRuntimeMetadata.json" - $dotnetMetadataJson = Get-Content $dotnetMetadataPath -Raw | ConvertFrom-Json - - # Channel is like: $Channel = "5.0.1xx-preview2" - $Channel = $dotnetMetadataJson.sdk.channel - - $sdkVersion = (Get-Content "$repoRoot/global.json" -Raw | ConvertFrom-Json).sdk.version - Import-Module "$repoRoot/build.psm1" -Force - - Find-Dotnet - - if(-not (Get-PackageSource -Name 'dotnet' -ErrorAction SilentlyContinue)) - { - $nugetFeed = ([xml](Get-Content $repoRoot/nuget.config -Raw)).Configuration.packagesources.add | Where-Object { $_.Key -eq 'dotnet' } | Select-Object -ExpandProperty Value - if ($nugetFeed) { - Register-PackageSource -Name 'dotnet' -Location $nugetFeed -ProviderName NuGet - Write-Verbose -Message "Register new package source 'dotnet'" -verbose - } - } - - ## Install latest version from the channel - - #Install-Dotnet -Channel "$Channel" -Version $sdkVersion - Start-PSBootstrap - - Write-Verbose -Message "Installing .NET SDK completed." -Verbose - - displayName: Install .NET - env: - __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) + - template: /.pipelines/templates/install-dotnet.yml@self - pwsh: | $repoRoot = "$(Build.SourcesDirectory)/PowerShell" - $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 - Import-Module "$repoRoot/build.psm1" -Force - Start-PSBootstrap + + Import-Module "$repoRoot/build.psm1" -Force -Verbose + Start-PSBootstrap -Scenario Dotnet $toolPath = New-Item -ItemType Directory "$(System.DefaultWorkingDirectory)/toolPath" | Select-Object -ExpandProperty FullName @@ -85,7 +54,7 @@ jobs: $packageName = '${{ parameters.globalToolPackageName }}' Write-Verbose -Verbose "Installing $packageName" - dotnet tool install --add-source "$ENV:PIPELINE_WORKSPACE/PSPackagesOfficial/drop_nupkg_build_nupkg" --tool-path $toolPath --version '$(Version)' $packageName + dotnet tool install --add-source "$ENV:PIPELINE_WORKSPACE/PSPackagesOfficial/drop_nupkg_build_nupkg" --tool-path $toolPath --version '$(OutputVersion.Version)' $packageName Get-ChildItem -Path $toolPath @@ -108,8 +77,9 @@ jobs: - pwsh: | $repoRoot = "$(Build.SourcesDirectory)/PowerShell" - Import-Module "$repoRoot/build.psm1" -Force - Start-PSBootstrap + + Import-Module "$repoRoot/build.psm1" -Force -Verbose + Start-PSBootstrap -Scenario Dotnet $exeName = if ($IsWindows) { "pwsh.exe" } else { "pwsh" } @@ -133,7 +103,7 @@ jobs: $versionFound = & $toolPath -c '$PSVersionTable.PSVersion.ToString()' - if ( '$(Version)' -ne $versionFound) + if ( '$(OutputVersion.Version)' -ne $versionFound) { throw "Expected version of global tool not found. Installed version is $versionFound" } diff --git a/.pipelines/templates/release-validate-packagenames.yml b/.pipelines/templates/release-validate-packagenames.yml index f84950a1a61..5953366ffd7 100644 --- a/.pipelines/templates/release-validate-packagenames.yml +++ b/.pipelines/templates/release-validate-packagenames.yml @@ -16,30 +16,18 @@ 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 displayName: Capture environment - pwsh: | - $name = "{0}_{1:x}" -f '$(releaseTag)', (Get-Date).Ticks + $name = "{0}_{1:x}" -f '$(OutputReleaseTag.releaseTag)', (Get-Date).Ticks Write-Host $name Write-Host "##vso[build.updatebuildnumber]$name" displayName: Set Release Name - - pwsh: | - $azureRmModule = Get-InstalledModule AzureRM -ErrorAction SilentlyContinue -Verbose - if ($azureRmModule) { - Write-Host 'AzureRM module exists. Removing it' - Uninstall-AzureRm - Write-Host 'AzureRM module removed' - } - - Install-Module -Name Az.Storage -Force -AllowClobber -Scope CurrentUser -Verbose - - displayName: Remove AzRM modules and install Az.Storage - - task: AzurePowerShell@5 displayName: Upload packages to blob inputs: @@ -50,7 +38,7 @@ jobs: inline: | $storageAccount = Get-AzStorageAccount -ResourceGroupName '$(StorageResourceGroup)' -Name '$(StorageAccount)' $ctx = $storageAccount.Context - $container = '$(AzureVersion)' + $container = '$(OutputVersion.AzureVersion)' $destinationPath = '$(System.ArtifactsDirectory)' $blobList = Get-AzStorageBlob -Container $container -Context $ctx @@ -94,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 @@ -106,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 @@ -116,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 82102ec3bfe..3b0442f65d6 100644 --- a/.pipelines/templates/release-validate-sdk.yml +++ b/.pipelines/templates/release-validate-sdk.yml @@ -46,47 +46,15 @@ jobs: Get-ChildItem "$(Pipeline.Workspace)/PSPackagesOfficial/drop_nupkg_build_nupkg" -Recurse displayName: 'Capture Downloaded Artifacts' - - pwsh: | - $repoRoot = "$(Build.SourcesDirectory)" - - $dotnetMetadataPath = "$repoRoot/DotnetRuntimeMetadata.json" - $dotnetMetadataJson = Get-Content $dotnetMetadataPath -Raw | ConvertFrom-Json - - # Channel is like: $Channel = "5.0.1xx-preview2" - $Channel = $dotnetMetadataJson.sdk.channel - - $sdkVersion = (Get-Content "$repoRoot/global.json" -Raw | ConvertFrom-Json).sdk.version - Import-Module "$repoRoot/build.psm1" -Force - - Find-Dotnet - - if(-not (Get-PackageSource -Name 'dotnet' -ErrorAction SilentlyContinue)) - { - $nugetFeed = ([xml](Get-Content $repoRoot/nuget.config -Raw)).Configuration.packagesources.add | Where-Object { $_.Key -eq 'dotnet' } | Select-Object -ExpandProperty Value - - if ($nugetFeed) { - Register-PackageSource -Name 'dotnet' -Location $nugetFeed -ProviderName NuGet - Write-Verbose -Message "Register new package source 'dotnet'" -verbose - } - } - - ## Install latest version from the channel - #Install-Dotnet -Channel "$Channel" -Version $sdkVersion - - Start-PSBootstrap - - Write-Verbose -Message "Installing .NET SDK completed." -Verbose - - displayName: Install .NET - env: - __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) + - template: /.pipelines/templates/install-dotnet.yml@self - pwsh: | $repoRoot = "$(Build.SourcesDirectory)" - $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 - Import-Module "$repoRoot/build.psm1" -Force - Start-PSBootstrap + Import-Module "$repoRoot/build.psm1" -Force -Verbose + Start-PSBootstrap -Scenario Dotnet + + $env:DOTNET_NOLOGO=1 $localLocation = "$(Pipeline.Workspace)/PSPackagesOfficial/drop_nupkg_build_nupkg" $xmlElement = @" @@ -95,7 +63,7 @@ jobs: "@ - $releaseVersion = '$(Version)' + $releaseVersion = '$(OutputVersion.Version)' Write-Verbose -Message "Release Version: $releaseVersion" -Verbose @@ -116,10 +84,6 @@ jobs: Get-Content $nugetPath - # Add workaround to unblock xUnit testing see issue: https://github.com/dotnet/sdk/issues/26462 - $dotnetPath = if ($IsWindows) { "$env:LocalAppData\Microsoft\dotnet" } else { "$env:HOME/.dotnet" } - $env:DOTNET_ROOT = $dotnetPath - dotnet --info dotnet restore dotnet test /property:RELEASE_VERSION=$releaseVersion --test-adapter-path:. "--logger:xunit;LogFilePath=$(System.DefaultWorkingDirectory)/test-hosting.xml" 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 039e9336d7c..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,18 +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 - - pwsh: | - Import-Module $(Build.SourcesDirectory)/PowerShell/build.psm1 - Start-PSBootstrap - displayName: Bootstrap - env: - __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) - ob_restore_phase: true + - template: /.pipelines/templates/install-dotnet.yml@self - pwsh: | New-Item -Path '$(ob_outputDirectory)' -ItemType Directory -Force @@ -75,8 +71,6 @@ jobs: - job: build_testartifacts_nonwin variables: - - name: runCodesignValidationInjection - value: false - name: NugetSecurityAnalysisWarningLevel value: none - group: DotNetPrivateBuildAccess @@ -92,18 +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 - - pwsh: | - Import-Module $(Build.SourcesDirectory)/PowerShell/build.psm1 - Start-PSBootstrap - displayName: Bootstrap - env: - __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) - 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 1a5186e5e6a..de6f8e0d7fd 100644 --- a/.pipelines/templates/uploadToAzure.yml +++ b/.pipelines/templates/uploadToAzure.yml @@ -6,12 +6,10 @@ jobs: type: windows variables: - name: ob_sdl_sbom_enabled - value: false - - name: runCodesignValidationInjection - value: false + value: true - 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,45 +225,40 @@ 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' } | - # ForEach-Object { Write-Verbose -Verbose $_.FullName ; $_ } | - # Copy-Item -Destination $(ob_outputDirectory)/GitHubPackages -Recurse - # - # 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' } | - # ForEach-Object { Write-Verbose -Verbose $_.FullName ; $_ } | - # Copy-Item -Destination $(ob_outputDirectory)/NuGetPackages -Recurse - # displayName: Copy downloads to Artifacts + - 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'} | + 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' } | + Copy-Item -Destination $(ob_outputDirectory)/NuGetPackages -Recurse -Verbose + displayName: Copy downloads to Artifacts - pwsh: | # Create output directory for packages which have been uploaded to blob storage New-Item -Path $(Build.ArtifactStagingDirectory)/uploaded -ItemType Directory -Force displayName: Create output directory for packages - - pwsh: | - $azureRmModule = Get-InstalledModule AzureRM -ErrorAction SilentlyContinue -Verbose - if ($azureRmModule) { - Write-Host 'AzureRM module exists. Removing it' - Uninstall-AzureRm - Write-Host 'AzureRM module removed' - } - - Install-Module -Name Az.Storage -Force -AllowClobber -Scope CurrentUser -Verbose - - displayName: Remove AzRM modules - - task: AzurePowerShell@5 displayName: Upload packages to blob inputs: @@ -400,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 @@ -411,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 75% rename from .pipelines/templates/variable/release-shared.yml rename to .pipelines/templates/variables/release-shared.yml index 92ab56199d4..70d3dd2df97 100644 --- a/.pipelines/templates/variable/release-shared.yml +++ b/.pipelines/templates/variables/release-shared.yml @@ -5,15 +5,19 @@ parameters: - name: SBOM type: boolean default: false + - name: RELEASETAG + type: string + default: 'Not Initialized' + - name: VERSION + type: string + default: 'Not Initialized' variables: - name: ob_signing_setup_enabled 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' @@ -30,3 +34,7 @@ variables: value: ${{ parameters.REPOROOT }}\.config\suppress.json - name: ob_sdl_codeql_compiled_enabled value: false + - name: ReleaseTag + value: ${{ parameters.RELEASETAG }} + - name: Version + value: ${{ parameters.VERSION }} diff --git a/.pipelines/templates/windows-hosted-build.yml b/.pipelines/templates/windows-hosted-build.yml index d8d5811df66..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,6 +63,8 @@ jobs: AnalyzeInPipeline: false Language: csharp + - template: /.pipelines/templates/install-dotnet.yml@self + - pwsh: | $runtime = switch ($env:Architecture) { @@ -88,7 +88,7 @@ jobs: Import-Module -Name $(PowerShellRoot)/build.psm1 -Force $buildWithSymbolsPath = New-Item -ItemType Directory -Path $(Pipeline.Workspace)/Symbols_$(Architecture) -Force - Start-PSBootstrap -Package + Start-PSBootstrap -Scenario Package $null = New-Item -ItemType Directory -Path $buildWithSymbolsPath -Force -Verbose $ReleaseTagParam = @{} @@ -137,7 +137,7 @@ jobs: } Import-Module -Name $(PowerShellRoot)/build.psm1 -Force - Start-PSBootstrap + Find-Dotnet ## Build global tool Write-Verbose -Message "Building PowerShell global tool for Windows.x64" -Verbose @@ -200,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') }}: @@ -207,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' @@ -232,7 +234,8 @@ jobs: #> Import-Module -Name $(PowerShellRoot)/build.psm1 -Force - Start-PSBootstrap + Find-Dotnet + $packagingStrings = Import-PowerShellDataFile "$(PowerShellRoot)\tools\packaging\packaging.strings.psd1" $outputPath = Join-Path '$(ob_outputDirectory)' 'globaltool' @@ -267,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 $_ @@ -279,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' @@ -287,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 @@ -312,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 new file mode 100644 index 00000000000..b90ab0d9eb4 --- /dev/null +++ b/.vsts-ci/linux-internal.yml @@ -0,0 +1,115 @@ +# Pipeline to run Linux CI internally +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/* + - .github/workflows/* + - .dependabot/config.yml + - .pipelines/* + - test/perf/* +pr: + branches: + include: + - master + - release* + - feature* + paths: + include: + - '*' + exclude: + - .dependabot/config.yml + - .github/ISSUE_TEMPLATE/* + - .github/workflows/* + - .vsts-ci/misc-analysis.yml + - .vsts-ci/windows.yml + - .vsts-ci/windows/* + - tools/cgmanifest/* + - LICENSE.txt + - test/common/markdown/* + - test/perf/* + - tools/releaseBuild/* + - tools/install* + - tools/releaseBuild/azureDevOps/templates/* + - README.md + - .spelling + - .pipelines/* + +variables: + DOTNET_CLI_TELEMETRY_OPTOUT: 1 + POWERSHELL_TELEMETRY_OPTOUT: 1 + DOTNET_NOLOGO: 1 + __SuppressAnsiEscapeSequences: 1 + nugetMultiFeedWarnLevel: none + +resources: + repositories: + - repository: Docker + type: github + endpoint: PowerShell + name: PowerShell/PowerShell-Docker + ref: master + +stages: +- stage: BuildLinuxStage + displayName: Build for Linux + jobs: + - template: templates/ci-build.yml + parameters: + pool: ubuntu-20.04 + jobName: linux_build + displayName: linux Build + +- stage: TestUbuntu + displayName: Test for Ubuntu + dependsOn: [BuildLinuxStage] + jobs: + - template: templates/nix-test.yml + parameters: + name: Ubuntu + pool: ubuntu-20.04 + purpose: UnelevatedPesterTests + tagSet: CI + + - template: templates/nix-test.yml + parameters: + name: Ubuntu + pool: ubuntu-20.04 + purpose: ElevatedPesterTests + tagSet: CI + + - template: templates/nix-test.yml + parameters: + name: Ubuntu + pool: ubuntu-20.04 + purpose: UnelevatedPesterTests + tagSet: Others + + - template: templates/nix-test.yml + parameters: + name: Ubuntu + pool: ubuntu-20.04 + purpose: ElevatedPesterTests + tagSet: Others + + - template: templates/verify-xunit.yml + parameters: + pool: ubuntu-20.04 + +- stage: PackageLinux + displayName: Package Linux + dependsOn: ["BuildLinuxStage"] + jobs: + - template: linux/templates/packaging.yml + parameters: + pool: ubuntu-20.04 diff --git a/.vsts-ci/linux.yml b/.vsts-ci/linux.yml index c1a1fd5c0ab..5d9dc663e1c 100644 --- a/.vsts-ci/linux.yml +++ b/.vsts-ci/linux.yml @@ -34,30 +34,21 @@ pr: - feature* paths: include: - - '*' - exclude: - - .dependabot/config.yml - - .github/ISSUE_TEMPLATE/* - - .github/workflows/* - - .vsts-ci/misc-analysis.yml - - .vsts-ci/windows.yml - - .vsts-ci/windows/* - - tools/cgmanifest.json - - LICENSE.txt - - test/common/markdown/* - - test/perf/* - - tools/releaseBuild/* - - tools/install* - - tools/releaseBuild/azureDevOps/templates/* - - README.md - - .spelling - - .pipelines/* + - .vsts-ci/linux.yml + - .vsts-ci/linux/templates/packaging.yml + - assets/manpage/* + - build.psm1 + - global.json + - nuget.config + - PowerShell.Common.props + - src/*.csproj + - tools/ci.psm1 + - tools/packaging/* 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 @@ -75,101 +66,14 @@ stages: jobs: - template: templates/ci-build.yml parameters: - pool: ubuntu-20.04 + pool: ubuntu-latest jobName: linux_build displayName: linux Build -- stage: TestUbuntu - displayName: Test for Ubuntu - dependsOn: [BuildLinuxStage] - jobs: - - template: templates/nix-test.yml - parameters: - name: Ubuntu - pool: ubuntu-20.04 - purpose: UnelevatedPesterTests - tagSet: CI - - - template: templates/nix-test.yml - parameters: - name: Ubuntu - pool: ubuntu-20.04 - purpose: ElevatedPesterTests - tagSet: CI - - - template: templates/nix-test.yml - parameters: - name: Ubuntu - pool: ubuntu-20.04 - purpose: UnelevatedPesterTests - tagSet: Others - - - template: templates/nix-test.yml - parameters: - name: Ubuntu - pool: ubuntu-20.04 - purpose: ElevatedPesterTests - tagSet: Others - - - template: templates/verify-xunit.yml - parameters: - pool: ubuntu-20.04 - -- stage: TestContainer - displayName: Test in a container - dependsOn: [BuildLinuxStage] - jobs: - - job: getContainerJob - displayName: Choose a container - pool: - vmImage: ubuntu-20.04 - steps: - - checkout: self - clean: true - - - checkout: Docker - clean: true - - - pwsh: | - # Initialize container test stage - Import-Module ./PowerShell/tools/ci.psm1 - Invoke-InitializeContainerStage -ContainerPattern '${{ parameters.ContainerPattern }}' - name: getContainerTask - displayName: Initialize Container Stage - continueOnError: true - - - template: templates/test/nix-container-test.yml - parameters: - name: container - pool: ubuntu-20.04 - purpose: UnelevatedPesterTests - tagSet: CI - - - template: templates/test/nix-container-test.yml - parameters: - name: container - pool: ubuntu-20.04 - purpose: ElevatedPesterTests - tagSet: CI - - - template: templates/test/nix-container-test.yml - parameters: - name: container - pool: ubuntu-20.04 - purpose: UnelevatedPesterTests - tagSet: Others - - - template: templates/test/nix-container-test.yml - parameters: - name: container - pool: ubuntu-20.04 - purpose: ElevatedPesterTests - tagSet: Others - - stage: PackageLinux displayName: Package Linux dependsOn: ["BuildLinuxStage"] jobs: - template: linux/templates/packaging.yml parameters: - pool: ubuntu-20.04 + pool: ubuntu-latest diff --git a/.vsts-ci/linux/templates/packaging.yml b/.vsts-ci/linux/templates/packaging.yml index 47652b1b2e2..8f77b8e24a0 100644 --- a/.vsts-ci/linux/templates/packaging.yml +++ b/.vsts-ci/linux/templates/packaging.yml @@ -13,6 +13,12 @@ jobs: displayName: ${{ parameters.name }} packaging steps: + - task: UseDotNet@2 + displayName: 'Use .NET Core sdk' + inputs: + useGlobalJson: true + packageType: 'sdk' + - pwsh: | Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose displayName: Capture Environment @@ -33,7 +39,7 @@ jobs: - pwsh: | Import-Module .\build.psm1 - Start-PSBootstrap -Package + Start-PSBootstrap -Scenario Package displayName: Bootstrap - pwsh: | diff --git a/.vsts-ci/mac.yml b/.vsts-ci/mac.yml index bfb0b3afd21..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 @@ -110,6 +109,6 @@ stages: clean: true - pwsh: | import-module ./build.psm1 - start-psbootstrap -package + start-psbootstrap -Scenario package displayName: Bootstrap packaging condition: succeededOrFailed() diff --git a/.vsts-ci/misc-analysis.yml b/.vsts-ci/misc-analysis.yml deleted file mode 100644 index 15b3277635b..00000000000 --- a/.vsts-ci/misc-analysis.yml +++ /dev/null @@ -1,48 +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 - - feature* - -pr: - branches: - include: - - master - - feature* - -resources: - repositories: - - repository: ComplianceRepo - type: github - endpoint: PowerShell - name: PowerShell/compliance - ref: master - -variables: - - name: repoFolder - value: PowerShell - -stages: -- stage: Compliance - jobs: - - job: CI_Compliance - displayName: CI Compliance - - pool: - vmImage: windows-latest - - variables: - - name: repoPath - value: $(Agent.BuildDirectory)\$(repoFolder) - - steps: - - checkout: self - clean: true - path: $(repoFolder) - - - checkout: ComplianceRepo - - - template: ci-compliance.yml@ComplianceRepo diff --git a/.vsts-ci/psresourceget-acr.yml b/.vsts-ci/psresourceget-acr.yml index c4211d35d95..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 @@ -137,7 +136,6 @@ stages: - pwsh: | Import-Module .\build.psm1 -force - Start-PSBootstrap Import-Module .\tools\ci.psm1 Restore-PSOptions -PSOptionsPath '$(System.ArtifactsDirectory)\build\psoptions.json' $options = (Get-PSOptions) @@ -155,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/ci-build.yml b/.vsts-ci/templates/ci-build.yml index b9f7ad8573c..5ec458c3c5a 100644 --- a/.vsts-ci/templates/ci-build.yml +++ b/.vsts-ci/templates/ci-build.yml @@ -57,6 +57,12 @@ jobs: - ${{ if ne(variables['UseAzDevOpsFeed'], '') }}: - template: /tools/releaseBuild/azureDevOps/templates/insert-nuget-config-azfeed.yml + - task: UseDotNet@2 + displayName: 'Use .NET Core sdk' + inputs: + useGlobalJson: true + packageType: 'sdk' + - pwsh: | Import-Module .\tools\ci.psm1 Invoke-CIInstall -SkipUser 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/nix-test.yml b/.vsts-ci/templates/nix-test.yml index ab3985dacd6..214ae14b2c6 100644 --- a/.vsts-ci/templates/nix-test.yml +++ b/.vsts-ci/templates/nix-test.yml @@ -13,6 +13,12 @@ jobs: displayName: ${{ parameters.name }} Test - ${{ parameters.purpose }} - ${{ parameters.tagSet }} steps: + - task: UseDotNet@2 + displayName: 'Use .NET Core sdk' + inputs: + useGlobalJson: true + packageType: 'sdk' + - template: ./test/nix-test-steps.yml parameters: purpose: ${{ parameters.purpose }} diff --git a/.vsts-ci/templates/test/nix-container-test.yml b/.vsts-ci/templates/test/nix-container-test.yml index 931af6fc675..37c60a4c53b 100644 --- a/.vsts-ci/templates/test/nix-container-test.yml +++ b/.vsts-ci/templates/test/nix-container-test.yml @@ -23,6 +23,12 @@ jobs: displayName: ${{ parameters.name }} Test - ${{ parameters.purpose }} - ${{ parameters.tagSet }} steps: + - task: UseDotNet@2 + displayName: 'Use .NET Core sdk' + inputs: + useGlobalJson: true + packageType: 'sdk' + - template: ./nix-test-steps.yml parameters: purpose: ${{ parameters.purpose }} diff --git a/.vsts-ci/templates/windows-test.yml b/.vsts-ci/templates/windows-test.yml deleted file mode 100644 index a30f37a24ac..00000000000 --- a/.vsts-ci/templates/windows-test.yml +++ /dev/null @@ -1,86 +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 - - # 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 - Start-PSBootstrap - 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 3ab3e8e1f67..00000000000 --- a/.vsts-ci/windows-daily.yml +++ /dev/null @@ -1,146 +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() - - - 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 - Start-PSBootstrap - 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 - Start-PSBootstrap - 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 - Start-PSBootstrap - 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 - Start-PSBootstrap - 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 ca5352cb4e2..4171d09643d 100644 --- a/.vsts-ci/windows.yml +++ b/.vsts-ci/windows.yml @@ -25,29 +25,24 @@ pr: - feature* paths: include: - - '*' + - .vsts-ci/templates/* + - .vsts-ci/windows.yml + - '*.props' + - build.psm1 + - src/* + - test/* + - tools/buildCommon/* + - tools/ci.psm1 + - tools/WindowsCI.psm1 exclude: - - .dependabot/config.yml - - .github/ISSUE_TEMPLATE/* - - .github/workflows/* - - .vsts-ci/misc-analysis.yml - - tools/cgmanifest.json - - LICENSE.txt - test/common/markdown/* - test/perf/* - - tools/packaging/* - - tools/releaseBuild/* - - tools/releaseBuild/azureDevOps/templates/* - - README.md - - .spelling - - .pipelines/* 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/templates/windows-packaging.yml b/.vsts-ci/windows/templates/windows-packaging.yml index 72ee519319d..d23b745c30f 100644 --- a/.vsts-ci/windows/templates/windows-packaging.yml +++ b/.vsts-ci/windows/templates/windows-packaging.yml @@ -47,9 +47,6 @@ jobs: displayName: Capture PowerShell Version Table condition: succeededOrFailed() - - - template: /tools/releaseBuild/azureDevOps/templates/insert-nuget-config-azfeed.yml - - pwsh: | Import-Module .\tools\ci.psm1 Switch-PSNugetConfig -Source Public @@ -57,6 +54,13 @@ jobs: condition: succeeded() workingDirectory: $(repoPath) + - task: UseDotNet@2 + displayName: 'Use .NET Core sdk' + inputs: + useGlobalJson: true + packageType: 'sdk' + workingDirectory: $(repoPath) + - pwsh: | Import-Module .\tools\ci.psm1 Invoke-CIInstall -SkipUser 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 91ac95caa86..eb506a7ddbc 100644 --- a/CHANGELOG/7.4.md +++ b/CHANGELOG/7.4.md @@ -1,5 +1,422 @@ # 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 + +_This release is internal only. It is not available for download._ + +### Tools + +- Check GH token availability for `Get-Changelog` (#25156) + +### Build and Packaging Improvements + +
+ + + +

Update .NET SDK to 8.0.407

+ +
+ +
    +
  • Update branch for release (#25101)
  • +
  • Only build Linux for packaging changes (#25161)
  • +
  • Skip additional packages when generating component manifest (#25160)
  • +
  • Remove Az module installs and AzureRM uninstalls in pipeline (#25157)
  • +
  • Add GitHub Actions workflow to verify PR labels (#25158)
  • +
  • Update security extensions (#25099)
  • +
  • Make Component Manifest Updater use neutral target in addition to RID target (#25100)
  • +
+ +
+ +[7.4.9]: https://github.com/PowerShell/PowerShell/compare/v7.4.8...v7.4.9 + +## [7.4.8] + +### Notes + +_This release is internal only. It is not available for download._ + +### Build and Packaging Improvements + +
+ + + +

Update .NET SDK to 8.0.406

+ +
+ +
    +
  • Update branch for release (#25085) (#24884)
  • +
  • Add UseDotnet task for installing dotnet (#25080)
  • +
  • Add Justin Chung as PowerShell team member in releaseTools.psm1 (#25074)
  • +
  • Fix V-Pack download package name (#25078)
  • +
  • Fix MSIX stage in release pipeline (#25079)
  • +
  • Give the pipeline runs meaningful names (#25081)
  • +
  • Make sure the vPack pipeline does not produce an empty package (#25082)
  • +
  • Update CODEOWNERS (#25083)
  • +
  • Add setup dotnet action to the build composite action (#25084)
  • +
  • Remove AzDO credscan as it is now in GitHub (#25077)
  • +
  • Use workload identity service connection to download makeappx tool from storage account (#25075)
  • +
  • Update .NET SDK (#24993)
  • +
  • Fix GitHub Action filter overmatching (#24957)
  • +
  • Fix release branch filters (#24960)
  • +
  • Convert powershell/PowerShell-CI-macos to GitHub Actions (#24955)
  • +
  • Convert powershell/PowerShell-CI-linux to GitHub Actions (#24945)
  • +
  • Convert powershell/PowerShell-Windows-CI to GitHub Actions (#24932)
  • +
  • PMC parse state correctly from update command's response (#24860)
  • +
  • Add EV2 support for publishing PowerShell packages to PMC (#24857)
  • +
+ +
+ +[7.4.8]: https://github.com/PowerShell/PowerShell/compare/v7.4.7...v7.4.8 + +## [7.4.7] + +### Build and Packaging Improvements + +
+ + + +

Update .NET SDK to 8.0.405

+ +
+ +
    +
  • Update branch for release - Transitive - true - minor (#24546)
  • +
  • Fix backport mistake in #24429 (#24545)
  • +
  • Fix seed max value for Container Linux CI (#24510) (#24543)
  • +
  • Add a way to use only NuGet feed sources (#24528) (#24542)
  • +
  • Bump Microsoft.PowerShell.PSResourceGet to 1.0.6 (#24419)
  • +
  • Update path due to pool change (Internal 33083)
  • +
  • Update pool for "Publish BuildInfo" job (Internal 33082)
  • +
  • Add missing backports and new fixes (Internal 33077)
  • +
  • Port copy blob changes (Internal 33055)
  • +
  • Update firewall to monitor (Internal 33048)
  • +
  • Fix typo in release-MakeBlobPublic.yml (Internal 33046)
  • +
  • Update change log for 7.4.6 (Internal 33040)
  • +
  • Update changelog for v7.4.6 release (Internal 32983)
  • +
  • Fix backport issues with release pipeline (#24835)
  • +
  • Remove duplicated parameter (#24832)
  • +
  • Make the AssemblyVersion not change for servicing releases 7.4.7 and onward (#24821)
  • +
  • Add *.props and sort path filters for windows CI (#24822) (#24823)
  • +
  • Take the newest windows signature nuget packages (#24818)
  • +
  • Use work load identity service connection to download makeappx tool from storage account (#24817) (#24820)
  • +
  • Update path filters for Windows CI (#24809) (#24819)
  • +
  • Fixed release pipeline errors and switched to KS3 (#24751) (#24816)
  • +
  • Update branch for release - Transitive - true - minor (#24806)
  • +
  • Add ability to capture MSBuild Binary logs when restore fails (#24128) (#24799)
  • +
  • Download package from package build for generating vpack (#24481) (#24801)
  • +
  • Add a parameter that skips verify packages step (#24763) (#24803)
  • +
  • Fix Changelog content grab during GitHub Release (#24788) (#24804)
  • +
  • Add tool package download in publish nuget stage (#24790) (#24805)
  • +
  • Add CodeQL scanning to APIScan build (#24303) (#24800)
  • +
  • Deploy Box Update (#24632) (#24802)
  • +
+ +
+ +### Documentation and Help Content + +- Update notices file (#24810) + +[7.4.7]: https://github.com/PowerShell/PowerShell/compare/v7.4.6...v7.4.7 + ## [7.4.6] - 2024-10-22 ### Build and Packaging Improvements @@ -27,7 +444,7 @@
  • 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)
  • @@ -630,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)
  • @@ -638,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)
  • @@ -799,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 new file mode 100644 index 00000000000..b4f173bcff6 --- /dev/null +++ b/CHANGELOG/7.5.md @@ -0,0 +1,951 @@ +# 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 + +
    + + + +

    Update .NET SDK to 9.0.102

    + +
    + +
      +
    • Add tool package download in publish nuget stage (#24790) (#24792)
    • +
    • Fix Changelog content grab during GitHub Release (#24788) (#24791)
    • +
    • Mark build as latest stable (#24789)
    • +
    • [release/v7.5] Update branch for release - Transitive - true - minor (#24786)
    • +
    • Update Microsoft.PowerShell.PSResourceGet to 1.1.0 (#24767) (#24785)
    • +
    • Make the AssemblyVersion not change for servicing releases (#24667) (#24783)
    • +
    • Deploy Box Update (#24632) (#24779)
    • +
    • Update machine pool for copy blob and upload buildinfo stage (#24587) (#24776)
    • +
    • Update nuget publish to use Deploy Box (#24596) (#24597)
    • +
    • Added Deploy Box Product Pathway to GitHub Release and NuGet Release Pipelines (#24583) (#24595)
    • +
    + +
    + +### Documentation and Help Content + +- Update `HelpInfoUri` for 7.5 (#24610) (#24777) + +[7.5.0]: https://github.com/PowerShell/PowerShell/compare/v7.5.0-rc.1...v7.5.0 + +## [7.5.0-rc.1] - 2024-11-14 + +**NOTE:** Due to technical issues, release of packages to packages.microsoft.com ~and release to NuGet.org~ is delayed. + +### Build and Packaging Improvements + +
    + + + +

    Bump to .NET 9.0.100

    + +
    + +
      +
    • Update ThirdPartyNotices file (#24582) (#24536)
    • +
    • Bump to .NET 9.0.100 (#24576) (#24535)
    • +
    • Add a way to use only NuGet feed sources (#24528) (#24530)
    • +
    • Update PSResourceGet to v1.1.0-RC2 (#24512) (#24525)
    • +
    • Add PMC mapping for debian 12 (bookworm) (#24413) (#24518)
    • +
    • Bump .NET to 9.0.100-rc.2.24474.11 (#24509) (#24522)
    • +
    • Keep the roff file when gzipping it. (#24450) (#24520)
    • +
    • Checkin generated manpage (#24423) (#24519)
    • +
    • Update PSReadLine to 2.3.6 (#24380) (#24517)
    • +
    • Download package from package build for generating vpack (#24481) (#24521)
    • +
    • Delete the msix blob if it's already there (#24353) (#24516)
    • +
    • Add CodeQL scanning to APIScan build (#24303) (#24515)
    • +
    • Update vpack pipeline (#24281) (#24514)
    • +
    • Fix seed max value for Container Linux CI (#24510) (#24511)
    • +
    • Bring preview.5 release fixes to release/v7.5 (#24379) (#24368)
    • +
    • Add BaseUrl to buildinfo json file (#24376) (#24377)
    • +
    + +
    + +[7.5.0-rc.1]: https://github.com/PowerShell/PowerShell/compare/v7.5.0-preview.5...v7.5.0-rc.1 + +## [7.5.0-preview.5] - 2024-10-01 + +### Breaking Changes + +- Treat large `Enum` values as numbers in `ConvertTo-Json` (#20999) (#24304) + +### Engine Updates and Fixes + +- Fix how processor architecture is validated in `Import-Module` (#24265) (#24317) + +### Experimental Features + +### General Cmdlet Updates and Fixes + +- Add `-Force` parameter to `Resolve-Path` and `Convert-Path` cmdlets to support wildcard hidden files (#20981) (#24344) +- Add telemetry to track the use of features (#24247) (#24331) +- Treat large `Enum` values as numbers in `ConvertTo-Json` (#20999) (#24304) +- Make features `PSCommandNotFoundSuggestion`, `PSCommandWithArgs`, and `PSModuleAutoLoadSkipOfflineFiles` stable (#24246) (#24310) +- Handle global tool when prepending `$PSHome` to `PATH` (#24228) (#24307) + +### Tests + +- Fix cleanup in `PSResourceGet` test (#24339) (#24345) + +### Build and Packaging Improvements + +
    + + + +

    Bump .NET SDK to 9.0.100-rc.1.24452.12

    + +
    + +
      +
    • Fixed Test Scenario for Compress-PSResource (Internal 32696)
    • +
    • Add back local NuGet source for test packages (Internal 32693)
    • +
    • Fix typo in release-MakeBlobPublic.yml (Internal 32689)
    • +
    • Copy to static site instead of making blob public (#24269) (#24343)
    • +
    • Update Microsoft.PowerShell.PSResourceGet to 1.1.0-preview2 (#24300) (#24337)
    • +
    • Remove the MD5 branch in the strong name signing token calculation (#24288) (#24321)
    • +
    • Update experimental-feature json files (#24271) (#24319)
    • +
    • Add updated libicu dependency for Debian packages (#24301) (#24324)
    • +
    • Add mapping to AzureLinux repo (#24290) (#24322)
    • +
    • Update and add new NuGet package sources for different environments. (#24264) (#24316)
    • +
    • Bump .NET 9 to 9.0.100-rc.1.24452.12 (#24273) (#24320)
    • +
    • Make some release tests run in a hosted pools (#24270) (#24318)
    • +
    • Do not build the exe for Global tool shim project (#24263) (#24315)
    • +
    • Delete assets/AppImageThirdPartyNotices.txt (#24256) (#24313)
    • +
    • Create new pipeline for compliance (#24252) (#24312)
    • +
    • Add specific path for issues in tsaconfig (#24244) (#24309)
    • +
    • Use Managed Identity for APIScan authentication (#24243) (#24308)
    • +
    • Add Windows signing for pwsh.exe (#24219) (#24306)
    • +
    • Check Create and Submit in vPack build by default (#24181) (#24305)
    • +
    + +
    + +### Documentation and Help Content + +- Delete demos directory (#24258) (#24314) + +[7.5.0-preview.5]: https://github.com/PowerShell/PowerShell/compare/v7.5.0-preview.4...v7.5.0-preview.5 + +## [7.5.0-preview.4] - 2024-08-28 + +### Engine Updates and Fixes + +- RecommendedAction: Explicitly start and stop ANSI Error Color (#24065) (Thanks @JustinGrote!) +- Improve .NET overload definition of generic methods (#21326) (Thanks @jborean93!) +- Optimize the `+=` operation for a collection when it's an object array (#23901) (Thanks @jborean93!) +- Allow redirecting to a variable as experimental feature `PSRedirectToVariable` (#20381) + +### General Cmdlet Updates and Fixes + +- Change type of `LineNumber` to `ulong` in `Select-String` (#24075) (Thanks @Snowman-25!) +- Fix `Invoke-RestMethod` to allow `-PassThru` and `-Outfile` work together (#24086) (Thanks @jshigetomi!) +- Fix Hyper-V Remoting when the module is imported via implicit remoting (#24032) (Thanks @jborean93!) +- Add `ConvertTo-CliXml` and `ConvertFrom-CliXml` cmdlets (#21063) (Thanks @ArmaanMcleod!) +- Add `OutFile` property in `WebResponseObject` (#24047) (Thanks @jshigetomi!) +- Show filename in `Invoke-WebRequest -OutFile -Verbose` (#24041) (Thanks @jshigetomi!) +- `Set-Acl`: Do not fail on untranslatable SID (#21096) (Thanks @jborean93!) +- Fix the extent of the parser error when a number constant is invalid (#24024) +- Fix `Move-Item` to throw error when moving into itself (#24004) +- Fix up .NET method invocation with `Optional` argument (#21387) (Thanks @jborean93!) +- Fix progress calculation on `Remove-Item` (#23869) (Thanks @jborean93!) +- Fix WebCmdlets when `-Body` is specified but `ContentType` is not (#23952) (Thanks @CarloToso!) +- Enable `-NoRestart` to work with `Register-PSSessionConfiguration` (#23891) +- Add `IgnoreComments` and `AllowTrailingCommas` options to `Test-Json` cmdlet (#23817) (Thanks @ArmaanMcleod!) +- Get-Help may report parameters with `ValueFromRemainingArguments` attribute as pipeline-able (#23871) + +### Code Cleanup + +
    + + + +

    We thank the following contributors!

    +

    @xtqqczze, @eltociear

    + +
    + +
      +
    • Minor cleanup on local variable names within a method (#24105)
    • +
    • Remove explicit IDE1005 suppressions (#21217) (Thanks @xtqqczze!)
    • +
    • Fix a typo in WebRequestSession.cs (#23963) (Thanks @eltociear!)
    • +
    + +
    + +### Tools + +- devcontainers: mount workspace in /PowerShell (#23857) (Thanks @rzippo!) + +### Tests + +- Add debugging to the MTU size test (#21463) + +### Build and Packaging Improvements + +
    + + + +

    We thank the following contributors!

    +

    @bosesubham2011

    + +
    + +
      +
    • Update third party notices (Internal 32128)
    • +
    • Update cgmanifest (#24163)
    • +
    • Fixes to Azure Public feed usage (#24149)
    • +
    • Add support for back porting PRs from GitHub or the Private Azure Repos (#20670)
    • +
    • Move to 9.0.0-preview.6.24327.7 (#24133)
    • +
    • update path (#24134)
    • +
    • Update to the latest NOTICES file (#24131)
    • +
    • Fix semver issue with updating cgmanifest (#24132)
    • +
    • Add ability to capture MSBuild Binary logs when restore fails (#24128)
    • +
    • add ability to skip windows stage (#24116)
    • +
    • chore: Refactor Nuget package source creation to use New-NugetPackageSource function (#24104)
    • +
    • Make Microsoft feeds the default (#24098)
    • +
    • Cleanup unused csproj (#23951)
    • +
    • Add script to update SDK version during release (#24034)
    • +
    • Enumerate over all signed zip packages (#24063)
    • +
    • Update metadata.json for PowerShell July releases (#24082)
    • +
    • Add macos signing for package files (#24015)
    • +
    • Update install-powershell.sh to support azure-linux (#23955) (Thanks @bosesubham2011!)
    • +
    • Skip build steps that do not have exe packages (#23945)
    • +
    • Update metadata.json for PowerShell June releases (#23973)
    • +
    • Create powershell.config.json for PowerShell.Windows.x64 global tool (#23941)
    • +
    • Fix error in the vPack release, debug script that blocked release (#23904)
    • +
    • Add vPack release (#23898)
    • +
    • Fix exe signing with third party signing for WiX engine (#23878)
    • +
    • Update wix installation in CI (#23870)
    • +
    • Add checkout to fix TSA config paths (#23865)
    • +
    • Merge the v7.5.0-preview.3 release branch to GitHub master branch
    • +
    • Update metadata.json for the v7.5.0-preview.3 release (#23862)
    • +
    • Bump PSResourceGet to 1.1.0-preview1 (#24129)
    • +
    • Bump github/codeql-action from 3.25.8 to 3.26.0 (#23953) (#23999) (#24053) (#24069) (#24095) (#24118)
    • +
    • Bump actions/upload-artifact from 4.3.3 to 4.3.6 (#24019) (#24113) (#24119)
    • +
    • Bump agrc/create-reminder-action from 1.1.13 to 1.1.15 (#24029) (#24043)
    • +
    • Bump agrc/reminder-action from 1.0.12 to 1.0.14 (#24028) (#24042)
    • +
    • Bump super-linter/super-linter from 5.7.2 to 6.8.0 (#23809) (#23856) (#23894) (#24030) (#24103)
    • +
    • Bump ossf/scorecard-action from 2.3.1 to 2.4.0 (#23802) (#24096)
    • +
    • Bump actions/dependency-review-action from 4.3.2 to 4.3.4 (#23897) (#24046)
    • +
    • Bump actions/checkout from 4.1.5 to 4.1.7 (#23813) (#23947)
    • +
    • Bump github/codeql-action from 3.25.4 to 3.25.8 (#23801) (#23893)
    • +
    + +
    + +### Documentation and Help Content + +- Update docs sample nuget.config (#24109) +- Update Code of Conduct and Security Policy (#23811) +- Update working-group-definitions.md for the Security WG (#23884) +- Fix up broken links in Markdown files (#23863) +- Update Engine Working Group Members (#23803) (Thanks @kilasuit!) +- Remove outdated and contradictory information from `README` (#23812) + +[7.5.0-preview.4]: https://github.com/PowerShell/PowerShell/compare/v7.5.0-preview.3...v7.5.0-preview.4 + +## [7.5.0-preview.3] - 2024-05-16 + +### Breaking Changes + +- Remember installation options and used them to initialize options for the next installation (#20420) (Thanks @reduckted!) +- `ConvertTo-Json`: Serialize `BigInteger` as a number (#21000) (Thanks @jborean93!) + +### Engine Updates and Fixes + +- Fix generating `OutputType` when running in Constrained Language Mode (#21605) +- Revert the PR #17856 (Do not preserve temporary results when no need to do so) (#21368) +- Make sure the assembly/library resolvers are registered at early stage (#21361) +- Fix PowerShell class to support deriving from an abstract class with abstract properties (#21331) +- Fix error formatting for pipeline enumeration exceptions (#20211) + +### General Cmdlet Updates and Fixes + +- Added progress bar for `Remove-Item` cmdlet (#20778) (Thanks @ArmaanMcleod!) +- Expand `~` to `$home` on Windows with tab completion (#21529) +- Separate DSC configuration parser check for ARM processor (#21395) (Thanks @dkontyko!) +- Fix `[semver]` type to pass `semver.org` tests (#21401) +- Don't complete when declaring parameter name and class member (#21182) (Thanks @MartinGC94!) +- Add `RecommendedAction` to `ConciseView` of the error reporting (#20826) (Thanks @JustinGrote!) +- Fix the error when using `Start-Process -Credential` without the admin privilege (#21393) (Thanks @jborean93!) +- Fix `Test-Path -IsValid` to check for invalid path and filename characters (#21358) +- Fix build failure due to missing reference in `GlobalToolShim.cs` (#21388) +- Fix argument passing in `GlobalToolShim` (#21333) (Thanks @ForNeVeR!) +- Make sure both stdout and stderr can be redirected from a native executable (#20997) +- Handle the case that `Runspace.DefaultRunspace == null` when logging for WDAC Audit (#21344) +- Fix a typo in `releaseTools.psm1` (#21306) (Thanks @eltociear!) +- `Get-Process`: Remove admin requirement for `-IncludeUserName` (#21302) (Thanks @jborean93!) +- Fall back to type inference when hashtable key-value cannot be retrieved from safe expression (#21184) (Thanks @MartinGC94!) +- Fix the regression when doing type inference for `$_` (#21223) (Thanks @MartinGC94!) +- Revert "Adjust PUT method behavior to POST one for default content type in WebCmdlets" (#21049) +- Fix a regression in `Format-Table` when header label is empty (#21156) + +### Code Cleanup + +
    + + + +

    We thank the following contributors!

    +

    @xtqqczze

    + +
    + +
      +
    • Enable CA1868: Unnecessary call to 'Contains' for sets (#21165) (Thanks @xtqqczze!)
    • +
    • Remove JetBrains.Annotations attributes (#21246) (Thanks @xtqqczze!)
    • +
    + +
    + +### Tests + +- Update `metadata.json` and `README.md` (#21454) +- Skip test on Windows Server 2012 R2 for `no-nl` (#21265) + +### Build and Packaging Improvements + +
    + + + +

    Bump to .NET 9.0.0-preview.3

    +

    We thank the following contributors!

    +

    @alerickson, @tgauth, @step-security-bot, @xtqqczze

    + +
    + +
      +
    • Fix PMC publish and the file path for msixbundle
    • +
    • Fix release version and stage issues in build and packaging
    • +
    • Add release tag if the environment variable is set
    • +
    • Update installation on Wix module (#23808)
    • +
    • Updates to package and release pipelines (#23800)
    • +
    • Update PSResourceGet to 1.0.5 (#23796)
    • +
    • Bump actions/upload-artifact from 4.3.2 to 4.3.3 (#21520)
    • +
    • Bump actions/dependency-review-action from 4.2.5 to 4.3.2 (#21560)
    • +
    • Bump actions/checkout from 4.1.2 to 4.1.5 (#21613)
    • +
    • Bump github/codeql-action from 3.25.1 to 3.25.4 (#22071)
    • +
    • Use feed with Microsoft Wix toolset (#21651) (Thanks @tgauth!)
    • +
    • Bump to .NET 9 preview 3 (#21782)
    • +
    • Use PSScriptRoot to find path to Wix module (#21611)
    • +
    • Create the Windows.x64 global tool with shim for signing (#21559)
    • +
    • Update Wix package install (#21537) (Thanks @tgauth!)
    • +
    • Add branch counter variables for daily package builds (#21523)
    • +
    • Use correct signing certificates for RPM and DEBs (#21522)
    • +
    • Revert to version available on Nuget for Microsoft.CodeAnalysis.Analyzers (#21515)
    • +
    • Official PowerShell Package pipeline (#21504)
    • +
    • Add a PAT for fetching PMC cli (#21503)
    • +
    • Bump ossf/scorecard-action from 2.0.6 to 2.3.1 (#21485)
    • +
    • Apply security best practices (#21480) (Thanks @step-security-bot!)
    • +
    • Bump Microsoft.CodeAnalysis.Analyzers (#21449)
    • +
    • Fix package build to not check some files for a signature. (#21458)
    • +
    • Update PSResourceGet version from 1.0.2 to 1.0.4.1 (#21439) (Thanks @alerickson!)
    • +
    • Verify environment variable for OneBranch before we try to copy (#21441)
    • +
    • Add back two transitive dependency packages (#21415)
    • +
    • Multiple fixes in official build pipeline (#21408)
    • +
    • Update PSReadLine to v2.3.5 (#21414)
    • +
    • PowerShell co-ordinated build OneBranch pipeline (#21364)
    • +
    • Add file description to pwsh.exe (#21352)
    • +
    • Suppress MacOS package manager output (#21244) (Thanks @xtqqczze!)
    • +
    • Update metadata.json and README.md (#21264)
    • +
    + +
    + +### Documentation and Help Content + +- Update the doc about how to build PowerShell (#21334) (Thanks @ForNeVeR!) +- Update the member lists for the Engine and Interactive-UX working groups (#20991) (Thanks @kilasuit!) +- Update CHANGELOG for `v7.2.19`, `v7.3.12` and `v7.4.2` (#21462) +- Fix grammar in `FAQ.md` (#21468) (Thanks @CodingGod987!) +- Fix typo in `SessionStateCmdletAPIs.cs` (#21413) (Thanks @eltociear!) +- Fix typo in a test (#21337) (Thanks @testwill!) +- Fix typo in `ast.cs` (#21350) (Thanks @eltociear!) +- Adding Working Group membership template (#21153) + +[7.5.0-preview.3]: https://github.com/PowerShell/PowerShell/compare/v7.5.0-preview.2...v7.5.0-preview.3 + +## [7.5.0-preview.2] - 2024-02-22 + +### Engine Updates and Fixes + +- Fix `using assembly` to use `Path.Combine` when constructing assembly paths (#21169) +- Validate the value for `using namespace` during semantic checks to prevent declaring invalid namespaces (#21162) + +### General Cmdlet Updates and Fixes + +- Add `WinGetCommandNotFound` and `CompletionPredictor` modules to track usage (#21040) +- `ConvertFrom-Json`: Add `-DateKind` parameter (#20925) (Thanks @jborean93!) +- Add tilde expansion for windows native executables (#20402) (Thanks @domsleee!) +- Add `DirectoryInfo` to the `OutputType` for `New-Item` (#21126) (Thanks @MartinGC94!) +- Fix `Get-Error` serialization of array values (#21085) (Thanks @jborean93!) + +### Code Cleanup + +
    + + + +

    We thank the following contributors!

    +

    @eltociear

    + +
    + +
      +
    • Fix a typo in CoreAdapter.cs (#21179) (Thanks @eltociear!)
    • +
    • Remove PSScheduledJob module source code (#21189)
    • +
    + +
    + +### Tests + +- Rewrite the mac syslog tests to make them less flaky (#21174) + +### Build and Packaging Improvements + +
    + + +

    Bump to .NET 9 Preview 1

    +

    We thank the following contributors!

    +

    @gregsdennis

    + +
    + +
      +
    • Bump to .NET 9 Preview 1 (#21229)
    • +
    • Add dotnet-runtime-9.0 as a dependency for the Mariner package
    • +
    • Add dotenv install as latest version does not work with current Ruby version (#21239)
    • +
    • Remove surrogateFile setting of APIScan (#21238)
    • +
    • Update experimental-feature json files (#21213)
    • +
    • Update to the latest NOTICES file (#21236)(#21177)
    • +
    • Update the cgmanifest (#21237)(#21093)
    • +
    • Update the cgmanifest (#21178)
    • +
    • Bump XunitXml.TestLogger from 3.1.17 to 3.1.20 (#21207)
    • +
    • Update versions of PSResourceGet (#21190)
    • +
    • Generate MSI for win-arm64 installer (#20516)
    • +
    • Bump JsonSchema.Net to v5.5.1 (#21120) (Thanks @gregsdennis!)
    • +
    + +
    + +### Documentation and Help Content + +- Update `README.md` and `metadata.json` for v7.5.0-preview.1 release (#21094) +- Fix incorrect examples in XML docs in `PowerShell.cs` (#21173) +- Update WG members (#21091) +- Update changelog for v7.4.1 (#21098) + +[7.5.0-preview.2]: https://github.com/PowerShell/PowerShell/compare/v7.5.0-preview.1...v7.5.0-preview.2 + +## [7.5.0-preview.1] - 2024-01-18 + +### Breaking Changes + +- Fix `-OlderThan` and `-NewerThan` parameters for `Test-Path` when using `PathType` and date range (#20942) (Thanks @ArmaanMcleod!) +- Previously `-OlderThan` would be ignored if specified together +- Change `New-FileCatalog -CatalogVersion` default to 2 (#20428) (Thanks @ThomasNieto!) + +### General Cmdlet Updates and Fixes + +- Fix completion crash for the SCCM provider (#20815, #20919, #20915) (Thanks @MartinGC94!) +- Fix regression in `Get-Content` when `-Tail 0` and `-Wait` are used together (#20734) (Thanks @CarloToso!) +- Add `Aliases` to the properties shown up when formatting the help content of the parameter returned by `Get-Help` (#20994) +- Add implicit localization fallback to `Import-LocalizedData` (#19896) (Thanks @chrisdent-de!) +- Change `Test-FileCatalog` to use `File.OpenRead` to better handle the case where the file is being used (#20939) (Thanks @dxk3355!) +- Added `-Module` completion for `Save-Help` and `Update-Help` commands (#20678) (Thanks @ArmaanMcleod!) +- Add argument completer to `-Verb` for `Start-Process` (#20415) (Thanks @ArmaanMcleod!) +- Add argument completer to `-Scope` for `*-Variable`, `*-Alias` & `*-PSDrive` commands (#20451) (Thanks @ArmaanMcleod!) +- Add argument completer to `-Verb` for `Get-Verb` and `Get-Command` (#20286) (Thanks @ArmaanMcleod!) +- Fixing incorrect formatting string in `CommandSearcher` trace logging (#20928) (Thanks @powercode!) +- Ensure the filename is not null when logging WDAC ETW events (#20910) (Thanks @jborean93!) +- Fix four regressions introduced by the WDAC logging feature (#20913) +- Leave the input, output, and error handles unset when they are not redirected (#20853) +- Fix `Start-Process -PassThru` to make sure the `ExitCode` property is accessible for the returned `Process` object (#20749) (Thanks @CodeCyclone!) +- Fix `Group-Object` output using interpolated strings (#20745) (Thanks @mawosoft!) +- Fix rendering of `DisplayRoot` for network `PSDrive` (#20793) +- Fix `Invoke-WebRequest` to report correct size when `-Resume` is specified (#20207) (Thanks @LNKLEO!) +- Add `PSAdapter` and `ConsoleGuiTools` to module load telemetry allow list (#20641) +- Fix Web Cmdlets to allow `WinForm` apps to work correctly (#20606) +- Block getting help from network locations in restricted remoting sessions (#20593) +- Fix `Group-Object` to use current culture for its output (#20608) +- Add argument completer to `-Version` for `Set-StrictMode` (#20554) (Thanks @ArmaanMcleod!) +- Fix `Copy-Item` progress to only show completed when all files are copied (#20517) +- Fix UNC path completion regression (#20419) (Thanks @MartinGC94!) +- Add telemetry to check for specific tags when importing a module (#20371) +- Report error if invalid `-ExecutionPolicy` is passed to `pwsh` (#20460) +- Add `HelpUri` to `Remove-Service` (#20476) +- Fix `unixmode` to handle `setuid` and `sticky` when file is not an executable (#20366) +- Fix `Test-Connection` due to .NET 8 changes (#20369) +- Fix implicit remoting proxy cmdlets to act on common parameters (#20367) +- Set experimental features to stable for 7.4 release (#20285) +- Revert changes to continue using `BinaryFormatter` for `Out-GridView` (#20300) +- Fix `Get-Service` non-terminating error message to include category (#20276) +- Prevent `Export-CSV` from flushing with every input (#20282) (Thanks @Chris--A!) +- Fix a regression in DSC (#20268) +- Include the module version in error messages when module is not found (#20144) (Thanks @ArmaanMcleod!) +- Add `-Empty` and `-InputObject` parameters to `New-Guid` (#20014) (Thanks @CarloToso!) +- Remove the comment trigger from feedback provider (#20136) +- Prevent fallback to file completion when tab completing type names (#20084) (Thanks @MartinGC94!) +- Add the alias `r` to the parameter `-Recurse` for the `Get-ChildItem` command (#20100) (Thanks @kilasuit!) + +### Code Cleanup + +
    + + + +

    We thank the following contributors!

    +

    @eltociear, @ImportTaste, @ThomasNieto, @0o001

    + +
    + +
      +
    • Fix typos in the code base (#20147, #20492, #20632, #21015, #20838) (Thanks @eltociear!)
    • +
    • Add the missing alias LP to -LiteralPath for some cmdlets (#20820) (Thanks @ImportTaste!)
    • +
    • Remove parenthesis for empty attribute parameters (#20087) (Thanks @ThomasNieto!)
    • +
    • Add space around keyword according to the CodeFactor rule (#20090) (Thanks @ThomasNieto!)
    • +
    • Remove blank lines as instructed by CodeFactor rules (#20086) (Thanks @ThomasNieto!)
    • +
    • Remove trailing whitespace (#20085) (Thanks @ThomasNieto!)
    • +
    • Fix typo in error message (#20145) (Thanks @0o001!)
    • +
    + +
    + +### Tools + +- Make sure feedback link in the bot's comment is clickable (#20878) (Thanks @floh96!) +- Fix bot so anyone who comments will remove the "Resolution-No Activity" label (#20788) +- Fix bot configuration to prevent multiple comments about "no activity" (#20758) +- Add bot logic for closing GitHub issues after 6 months of "no activity" (#20525) +- Refactor bot for easier use and updating (#20805) +- Configure bot to add survey comment for closed issues (#20397) + +### Tests + +- Suppress error output from `Set-Location` tests (#20499) +- Fix typo in `FileCatalog.Tests.ps1` (#20329) (Thanks @eltociear!) +- Continue to improve tests for release automation (#20182) +- Skip the test on x86 as `InstallDate` is not visible on `Wow64` (#20165) +- Harden some problematic release tests (#20155) + +### Build and Packaging Improvements + +
    + + + +

    We thank the following contributors!

    +

    @alerickson, @Zhoneym, @0o001

    + +
    + +
      +
    • Bump .NET SDK to 8.0.101 (#21084)
    • +
    • Update the cgmanifest (#20083, #20436, #20523, #20560, #20627, #20764, #20906, #20933, #20955, #21047)
    • +
    • Update to the latest NOTICES file (#20074, #20161, #20385, #20453, #20576, #20590, #20880, #20905)
    • +
    • Bump StyleCop.Analyzers from 1.2.0-beta.507 to 1.2.0-beta.556 (#20953)
    • +
    • Bump xUnit to 2.6.6 (#21071)
    • +
    • Bump JsonSchema.Net to 5.5.0 (#21027)
    • +
    • Fix failures in GitHub action markdown-link-check (#20996)
    • +
    • Bump xunit.runner.visualstudio to 2.5.6 (#20966)
    • +
    • Bump github/codeql-action from 2 to 3 (#20927)
    • +
    • Bump Markdig.Signed to 0.34.0 (#20926)
    • +
    • Bump Microsoft.ApplicationInsights from 2.21.0 to 2.22.0 (#20888)
    • +
    • Bump Microsoft.NET.Test.Sdk to 17.8.0 (#20660)
    • +
    • Update apiscan.yml to have access to the AzDevOpsArtifacts variable group (#20671)
    • +
    • Set the ollForwardOnNoCandidateFx in runtimeconfig.json to roll forward only on minor and patch versions (#20689)
    • +
    • Sign the global tool shim executable (#20794)
    • +
    • Bump actions/github-script from 6 to 7 (#20682)
    • +
    • Remove RHEL7 publishing to packages.microsoft.com as it's no longer supported (#20849)
    • +
    • Bump Microsoft.CodeAnalysis.CSharp to 4.8.0 (#20751)
    • +
    • Add internal nuget feed to compliance build (#20669)
    • +
    • Copy azure blob with PowerShell global tool to private blob and move to CDN during release (#20659)
    • +
    • Fix release build by making the internal SDK parameter optional (#20658)
    • +
    • Update PSResourceGet version to 1.0.1 (#20652)
    • +
    • Make internal .NET SDK URL as a parameter for release builld (#20655)
    • +
    • Fix setting of variable to consume internal SDK source (#20644)
    • +
    • Bump Microsoft.Management.Infrastructure to v3.0.0 (#20642)
    • +
    • Bump Microsoft.PowerShell.Native to v7.4.0 (#20617)
    • +
    • Bump Microsoft.Security.Extensions from 1.2.0 to 1.3.0 (#20556)
    • +
    • Fix package version for .NET nuget packages (#20551)
    • +
    • Add SBOM for release pipeline (#20519)
    • +
    • Block any preview vPack release (#20243)
    • +
    • Only registry App Path for release package (#20478)
    • +
    • Increase timeout when publishing packages to pacakages.microsoft.com (#20470)
    • +
    • Fix alpine tar package name and do not crossgen alpine fxdependent package (#20459)
    • +
    • Bump PSReadLine from 2.2.6 to 2.3.4 (#20305)
    • +
    • Remove the ref folder before running compliance (#20373)
    • +
    • Updates RIDs used to generate component Inventory (#20370)
    • +
    • Bump XunitXml.TestLogger from 3.1.11 to 3.1.17 (#20293)
    • +
    • Update experimental-feature json files (#20335)
    • +
    • Use fxdependent-win-desktop runtime for compliance runs (#20326)
    • +
    • Release build: Change the names of the PATs (#20307)
    • +
    • Add mapping for mariner arm64 stable (#20213)
    • +
    • Put the calls to Set-AzDoProjectInfo and Set-AzDoAuthToken in the right order (#20306)
    • +
    • Enable vPack provenance data (#20220)
    • +
    • Bump actions/checkout from 3 to 4 (#20205)
    • +
    • Start using new packages.microsoft.com cli (#20140, #20141)
    • +
    • Add mariner arm64 to PMC release (#20176)
    • +
    • Fix typo donet to dotnet in build scripts and pipelines (#20122) (Thanks @0o001!)
    • +
    • Install the pmc cli
    • +
    • Add skip publish parameter
    • +
    • Add verbose to clone
    • +
    + +
    + +### Documentation and Help Content + +- Include information about upgrading in readme (#20993) +- Expand "iff" to "if-and-only-if" in XML doc content (#20852) +- Update LTS links in README.md to point to the v7.4 packages (#20839) (Thanks @kilasuit!) +- Update `README.md` to improve readability (#20553) (Thanks @AnkitaSikdar005!) +- Fix link in `docs/community/governance.md` (#20515) (Thanks @suravshresth!) +- Update `ADOPTERS.md` (#20555) (Thanks @AnkitaSikdar005!) +- Fix a typo in `ADOPTERS.md` (#20504, #20520) (Thanks @shruti-sen2004!) +- Correct grammatical errors in `README.md` (#20509) (Thanks @alienishi!) +- Add 7.3 changelog URL to readme (#20473) (Thanks @Saibamen!) +- Clarify some comments and documentation (#20462) (Thanks @darkstar!) + +[7.5.0-preview.1]: https://github.com/PowerShell/PowerShell/compare/v7.4.1...v7.5.0-preview.1 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 f10594ccfb5..8f23d37c9bb 100644 --- a/CHANGELOG/preview.md +++ b/CHANGELOG/preview.md @@ -1,95 +1,61 @@ # Preview Changelog -## [7.5.0-preview.5] - 2024-10-01 +## [7.7.0-preview.1] ### Breaking Changes -- Treat large `Enum` values as numbers in `ConvertTo-Json` (#20999) (#24304) +- Add `ValidateNotNullOrEmpty` attribute to the `-Property` of `Format-Table/List/Custom` (#26552) +- Fix to use accurate message for validating a string argument is not null and not an empty string (#26668) +- Correct handling of explicit `-[Operator]:$false` parameter values in `Where-Object` (#26485) (Thanks @yotsuda!) ### Engine Updates and Fixes -- Fix how processor architecture is validated in `Import-Module` (#24265) (#24317) - -### Experimental Features - -### General Cmdlet Updates and Fixes - -- Add `-Force` parameter to `Resolve-Path` and `Convert-Path` cmdlets to support wildcard hidden files (#20981) (#24344) -- Add telemetry to track the use of features (#24247) (#24331) -- Treat large `Enum` values as numbers in `ConvertTo-Json` (#20999) (#24304) -- Make features `PSCommandNotFoundSuggestion`, `PSCommandWithArgs`, and `PSModuleAutoLoadSkipOfflineFiles` stable (#24246) (#24310) -- Handle global tool when prepending `$PSHome` to `PATH` (#24228) (#24307) - -### Tests - -- Fix cleanup in `PSResourceGet` test (#24339) (#24345) - -### Build and Packaging Improvements - -
    - - - -

    Bump .NET SDK to 9.0.100-rc.1.24452.12

    - -
    - -
      -
    • Fixed Test Scenario for Compress-PSResource (Internal 32696)
    • -
    • Add back local NuGet source for test packages (Internal 32693)
    • -
    • Fix typo in release-MakeBlobPublic.yml (Internal 32689)
    • -
    • Copy to static site instead of making blob public (#24269) (#24343)
    • -
    • Update Microsoft.PowerShell.PSResourceGet to 1.1.0-preview2 (#24300) (#24337)
    • -
    • Remove the MD5 branch in the strong name signing token calculation (#24288) (#24321)
    • -
    • Update experimental-feature json files (#24271) (#24319)
    • -
    • Add updated libicu dependency for Debian packages (#24301) (#24324)
    • -
    • Add mapping to AzureLinux repo (#24290) (#24322)
    • -
    • Update and add new NuGet package sources for different environments. (#24264) (#24316)
    • -
    • Bump .NET 9 to 9.0.100-rc.1.24452.12 (#24273) (#24320)
    • -
    • Make some release tests run in a hosted pools (#24270) (#24318)
    • -
    • Do not build the exe for Global tool shim project (#24263) (#24315)
    • -
    • Delete assets/AppImageThirdPartyNotices.txt (#24256) (#24313)
    • -
    • Create new pipeline for compliance (#24252) (#24312)
    • -
    • Add specific path for issues in tsaconfig (#24244) (#24309)
    • -
    • Use Managed Identity for APIScan authentication (#24243) (#24308)
    • -
    • Add Windows signing for pwsh.exe (#24219) (#24306)
    • -
    • Check Create and Submit in vPack build by default (#24181) (#24305)
    • -
    - -
    - -### Documentation and Help Content - -- Delete demos directory (#24258) (#24314) - -[7.5.0-preview.5]: https://github.com/PowerShell/PowerShell/compare/v7.5.0-preview.4...v7.5.0-preview.5 - -## [7.5.0-preview.4] - 2024-08-28 - -### Engine Updates and Fixes - -- RecommendedAction: Explicitly start and stop ANSI Error Color (#24065) (Thanks @JustinGrote!) -- Improve .NET overload definition of generic methods (#21326) (Thanks @jborean93!) -- Optimize the `+=` operation for a collection when it's an object array (#23901) (Thanks @jborean93!) -- Allow redirecting to a variable as experimental feature `PSRedirectToVariable` (#20381) +- 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 -- Change type of `LineNumber` to `ulong` in `Select-String` (#24075) (Thanks @Snowman-25!) -- Fix `Invoke-RestMethod` to allow `-PassThru` and `-Outfile` work together (#24086) (Thanks @jshigetomi!) -- Fix Hyper-V Remoting when the module is imported via implicit remoting (#24032) (Thanks @jborean93!) -- Add `ConvertTo-CliXml` and `ConvertFrom-CliXml` cmdlets (#21063) (Thanks @ArmaanMcleod!) -- Add `OutFile` property in `WebResponseObject` (#24047) (Thanks @jshigetomi!) -- Show filename in `Invoke-WebRequest -OutFile -Verbose` (#24041) (Thanks @jshigetomi!) -- `Set-Acl`: Do not fail on untranslatable SID (#21096) (Thanks @jborean93!) -- Fix the extent of the parser error when a number constant is invalid (#24024) -- Fix `Move-Item` to throw error when moving into itself (#24004) -- Fix up .NET method invocation with `Optional` argument (#21387) (Thanks @jborean93!) -- Fix progress calculation on `Remove-Item` (#23869) (Thanks @jborean93!) -- Fix WebCmdlets when `-Body` is specified but `ContentType` is not (#23952) (Thanks @CarloToso!) -- Enable `-NoRestart` to work with `Register-PSSessionConfiguration` (#23891) -- Add `IgnoreComments` and `AllowTrailingCommas` options to `Test-Json` cmdlet (#23817) (Thanks @ArmaanMcleod!) -- Get-Help may report parameters with `ValueFromRemainingArguments` attribute as pipeline-able (#23871) +- 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 @@ -98,149 +64,81 @@

    We thank the following contributors!

    -

    @xtqqczze, @eltociear

    +

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

      -
    • Minor cleanup on local variable names within a method (#24105)
    • -
    • Remove explicit IDE1005 suppressions (#21217) (Thanks @xtqqczze!)
    • -
    • Fix a typo in WebRequestSession.cs (#23963) (Thanks @eltociear!)
    • +
    • 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 -- devcontainers: mount workspace in /PowerShell (#23857) (Thanks @rzippo!) - -### Tests - -- Add debugging to the MTU size test (#21463) - -### Build and Packaging Improvements - -
    - - - -

    We thank the following contributors!

    -

    @bosesubham2011

    - -
    - -
      -
    • Update third party notices (Internal 32128)
    • -
    • Update cgmanifest (#24163)
    • -
    • Fixes to Azure Public feed usage (#24149)
    • -
    • Add support for back porting PRs from GitHub or the Private Azure Repos (#20670)
    • -
    • Move to 9.0.0-preview.6.24327.7 (#24133)
    • -
    • update path (#24134)
    • -
    • Update to the latest NOTICES file (#24131)
    • -
    • Fix semver issue with updating cgmanifest (#24132)
    • -
    • Add ability to capture MSBuild Binary logs when restore fails (#24128)
    • -
    • add ability to skip windows stage (#24116)
    • -
    • chore: Refactor Nuget package source creation to use New-NugetPackageSource function (#24104)
    • -
    • Make Microsoft feeds the default (#24098)
    • -
    • Cleanup unused csproj (#23951)
    • -
    • Add script to update SDK version during release (#24034)
    • -
    • Enumerate over all signed zip packages (#24063)
    • -
    • Update metadata.json for PowerShell July releases (#24082)
    • -
    • Add macos signing for package files (#24015)
    • -
    • Update install-powershell.sh to support azure-linux (#23955) (Thanks @bosesubham2011!)
    • -
    • Skip build steps that do not have exe packages (#23945)
    • -
    • Update metadata.json for PowerShell June releases (#23973)
    • -
    • Create powershell.config.json for PowerShell.Windows.x64 global tool (#23941)
    • -
    • Fix error in the vPack release, debug script that blocked release (#23904)
    • -
    • Add vPack release (#23898)
    • -
    • Fix exe signing with third party signing for WiX engine (#23878)
    • -
    • Update wix installation in CI (#23870)
    • -
    • Add checkout to fix TSA config paths (#23865)
    • -
    • Merge the v7.5.0-preview.3 release branch to GitHub master branch
    • -
    • Update metadata.json for the v7.5.0-preview.3 release (#23862)
    • -
    • Bump PSResourceGet to 1.1.0-preview1 (#24129)
    • -
    • Bump github/codeql-action from 3.25.8 to 3.26.0 (#23953) (#23999) (#24053) (#24069) (#24095) (#24118)
    • -
    • Bump actions/upload-artifact from 4.3.3 to 4.3.6 (#24019) (#24113) (#24119)
    • -
    • Bump agrc/create-reminder-action from 1.1.13 to 1.1.15 (#24029) (#24043)
    • -
    • Bump agrc/reminder-action from 1.0.12 to 1.0.14 (#24028) (#24042)
    • -
    • Bump super-linter/super-linter from 5.7.2 to 6.8.0 (#23809) (#23856) (#23894) (#24030) (#24103)
    • -
    • Bump ossf/scorecard-action from 2.3.1 to 2.4.0 (#23802) (#24096)
    • -
    • Bump actions/dependency-review-action from 4.3.2 to 4.3.4 (#23897) (#24046)
    • -
    • Bump actions/checkout from 4.1.5 to 4.1.7 (#23813) (#23947)
    • -
    • Bump github/codeql-action from 3.25.4 to 3.25.8 (#23801) (#23893)
    • -
    - -
    - -### Documentation and Help Content - -- Update docs sample nuget.config (#24109) -- Update Code of Conduct and Security Policy (#23811) -- Update working-group-definitions.md for the Security WG (#23884) -- Fix up broken links in Markdown files (#23863) -- Update Engine Working Group Members (#23803) (Thanks @kilasuit!) -- Remove outdated and contradictory information from `README` (#23812) - -[7.5.0-preview.4]: https://github.com/PowerShell/PowerShell/compare/v7.5.0-preview.3...v7.5.0-preview.4 - -## [7.5.0-preview.3] - 2024-05-16 - -### Breaking Changes - -- Remember installation options and used them to initialize options for the next installation (#20420) (Thanks @reduckted!) -- `ConvertTo-Json`: Serialize `BigInteger` as a number (#21000) (Thanks @jborean93!) - -### Engine Updates and Fixes - -- Fix generating `OutputType` when running in Constrained Language Mode (#21605) -- Revert the PR #17856 (Do not preserve temporary results when no need to do so) (#21368) -- Make sure the assembly/library resolvers are registered at early stage (#21361) -- Fix PowerShell class to support deriving from an abstract class with abstract properties (#21331) -- Fix error formatting for pipeline enumeration exceptions (#20211) - -### General Cmdlet Updates and Fixes - -- Added progress bar for `Remove-Item` cmdlet (#20778) (Thanks @ArmaanMcleod!) -- Expand `~` to `$home` on Windows with tab completion (#21529) -- Separate DSC configuration parser check for ARM processor (#21395) (Thanks @dkontyko!) -- Fix `[semver]` type to pass `semver.org` tests (#21401) -- Don't complete when declaring parameter name and class member (#21182) (Thanks @MartinGC94!) -- Add `RecommendedAction` to `ConciseView` of the error reporting (#20826) (Thanks @JustinGrote!) -- Fix the error when using `Start-Process -Credential` without the admin privilege (#21393) (Thanks @jborean93!) -- Fix `Test-Path -IsValid` to check for invalid path and filename characters (#21358) -- Fix build failure due to missing reference in `GlobalToolShim.cs` (#21388) -- Fix argument passing in `GlobalToolShim` (#21333) (Thanks @ForNeVeR!) -- Make sure both stdout and stderr can be redirected from a native executable (#20997) -- Handle the case that `Runspace.DefaultRunspace == null` when logging for WDAC Audit (#21344) -- Fix a typo in `releaseTools.psm1` (#21306) (Thanks @eltociear!) -- `Get-Process`: Remove admin requirement for `-IncludeUserName` (#21302) (Thanks @jborean93!) -- Fall back to type inference when hashtable key-value cannot be retrieved from safe expression (#21184) (Thanks @MartinGC94!) -- Fix the regression when doing type inference for `$_` (#21223) (Thanks @MartinGC94!) -- Revert "Adjust PUT method behavior to POST one for default content type in WebCmdlets" (#21049) -- Fix a regression in `Format-Table` when header label is empty (#21156) - -### Code Cleanup - -
    - - - -

    We thank the following contributors!

    -

    @xtqqczze

    - -
    - -
      -
    • Enable CA1868: Unnecessary call to 'Contains' for sets (#21165) (Thanks @xtqqczze!)
    • -
    • Remove JetBrains.Annotations attributes (#21246) (Thanks @xtqqczze!)
    • -
    - -
    +- 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 -- Update `metadata.json` and `README.md` (#21454) -- Skip test on Windows Server 2012 R2 for `no-nl` (#21265) +- 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 @@ -248,305 +146,107 @@ -

    Bump to .NET 9.0.0-preview.3

    We thank the following contributors!

    -

    @alerickson, @tgauth, @step-security-bot, @xtqqczze

    +

    @powercode, @kasperk81, @xtqqczze

      -
    • Fix PMC publish and the file path for msixbundle
    • -
    • Fix release version and stage issues in build and packaging
    • -
    • Add release tag if the environment variable is set
    • -
    • Update installation on Wix module (#23808)
    • -
    • Updates to package and release pipelines (#23800)
    • -
    • Update PSResourceGet to 1.0.5 (#23796)
    • -
    • Bump actions/upload-artifact from 4.3.2 to 4.3.3 (#21520)
    • -
    • Bump actions/dependency-review-action from 4.2.5 to 4.3.2 (#21560)
    • -
    • Bump actions/checkout from 4.1.2 to 4.1.5 (#21613)
    • -
    • Bump github/codeql-action from 3.25.1 to 3.25.4 (#22071)
    • -
    • Use feed with Microsoft Wix toolset (#21651) (Thanks @tgauth!)
    • -
    • Bump to .NET 9 preview 3 (#21782)
    • -
    • Use PSScriptRoot to find path to Wix module (#21611)
    • -
    • Create the Windows.x64 global tool with shim for signing (#21559)
    • -
    • Update Wix package install (#21537) (Thanks @tgauth!)
    • -
    • Add branch counter variables for daily package builds (#21523)
    • -
    • Use correct signing certificates for RPM and DEBs (#21522)
    • -
    • Revert to version available on Nuget for Microsoft.CodeAnalysis.Analyzers (#21515)
    • -
    • Official PowerShell Package pipeline (#21504)
    • -
    • Add a PAT for fetching PMC cli (#21503)
    • -
    • Bump ossf/scorecard-action from 2.0.6 to 2.3.1 (#21485)
    • -
    • Apply security best practices (#21480) (Thanks @step-security-bot!)
    • -
    • Bump Microsoft.CodeAnalysis.Analyzers (#21449)
    • -
    • Fix package build to not check some files for a signature. (#21458)
    • -
    • Update PSResourceGet version from 1.0.2 to 1.0.4.1 (#21439) (Thanks @alerickson!)
    • -
    • Verify environment variable for OneBranch before we try to copy (#21441)
    • -
    • Add back two transitive dependency packages (#21415)
    • -
    • Multiple fixes in official build pipeline (#21408)
    • -
    • Update PSReadLine to v2.3.5 (#21414)
    • -
    • PowerShell co-ordinated build OneBranch pipeline (#21364)
    • -
    • Add file description to pwsh.exe (#21352)
    • -
    • Suppress MacOS package manager output (#21244) (Thanks @xtqqczze!)
    • -
    • Update metadata.json and README.md (#21264)
    • +
    • 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 -- Update the doc about how to build PowerShell (#21334) (Thanks @ForNeVeR!) -- Update the member lists for the Engine and Interactive-UX working groups (#20991) (Thanks @kilasuit!) -- Update CHANGELOG for `v7.2.19`, `v7.3.12` and `v7.4.2` (#21462) -- Fix grammar in `FAQ.md` (#21468) (Thanks @CodingGod987!) -- Fix typo in `SessionStateCmdletAPIs.cs` (#21413) (Thanks @eltociear!) -- Fix typo in a test (#21337) (Thanks @testwill!) -- Fix typo in `ast.cs` (#21350) (Thanks @eltociear!) -- Adding Working Group membership template (#21153) - -[7.5.0-preview.3]: https://github.com/PowerShell/PowerShell/compare/v7.5.0-preview.2...v7.5.0-preview.3 - -## [7.5.0-preview.2] - 2024-02-22 - -### Engine Updates and Fixes - -- Fix `using assembly` to use `Path.Combine` when constructing assembly paths (#21169) -- Validate the value for `using namespace` during semantic checks to prevent declaring invalid namespaces (#21162) - -### General Cmdlet Updates and Fixes - -- Add `WinGetCommandNotFound` and `CompletionPredictor` modules to track usage (#21040) -- `ConvertFrom-Json`: Add `-DateKind` parameter (#20925) (Thanks @jborean93!) -- Add tilde expansion for windows native executables (#20402) (Thanks @domsleee!) -- Add `DirectoryInfo` to the `OutputType` for `New-Item` (#21126) (Thanks @MartinGC94!) -- Fix `Get-Error` serialization of array values (#21085) (Thanks @jborean93!) - -### Code Cleanup - -
    - - - -

    We thank the following contributors!

    -

    @eltociear

    - -
    - -
      -
    • Fix a typo in CoreAdapter.cs (#21179) (Thanks @eltociear!)
    • -
    • Remove PSScheduledJob module source code (#21189)
    • -
    - -
    - -### Tests - -- Rewrite the mac syslog tests to make them less flaky (#21174) - -### Build and Packaging Improvements - -
    - - -

    Bump to .NET 9 Preview 1

    -

    We thank the following contributors!

    -

    @gregsdennis

    - -
    - -
      -
    • Bump to .NET 9 Preview 1 (#21229)
    • -
    • Add dotnet-runtime-9.0 as a dependency for the Mariner package
    • -
    • Add dotenv install as latest version does not work with current Ruby version (#21239)
    • -
    • Remove surrogateFile setting of APIScan (#21238)
    • -
    • Update experimental-feature json files (#21213)
    • -
    • Update to the latest NOTICES file (#21236)(#21177)
    • -
    • Update the cgmanifest (#21237)(#21093)
    • -
    • Update the cgmanifest (#21178)
    • -
    • Bump XunitXml.TestLogger from 3.1.17 to 3.1.20 (#21207)
    • -
    • Update versions of PSResourceGet (#21190)
    • -
    • Generate MSI for win-arm64 installer (#20516)
    • -
    • Bump JsonSchema.Net to v5.5.1 (#21120) (Thanks @gregsdennis!)
    • -
    - -
    - -### Documentation and Help Content - -- Update `README.md` and `metadata.json` for v7.5.0-preview.1 release (#21094) -- Fix incorrect examples in XML docs in `PowerShell.cs` (#21173) -- Update WG members (#21091) -- Update changelog for v7.4.1 (#21098) - -[7.5.0-preview.2]: https://github.com/PowerShell/PowerShell/compare/v7.5.0-preview.1...v7.5.0-preview.2 - -## [7.5.0-preview.1] - 2024-01-18 - -### Breaking Changes - -- Fix `-OlderThan` and `-NewerThan` parameters for `Test-Path` when using `PathType` and date range (#20942) (Thanks @ArmaanMcleod!) -- Previously `-OlderThan` would be ignored if specified together -- Change `New-FileCatalog -CatalogVersion` default to 2 (#20428) (Thanks @ThomasNieto!) - -### General Cmdlet Updates and Fixes - -- Fix completion crash for the SCCM provider (#20815, #20919, #20915) (Thanks @MartinGC94!) -- Fix regression in `Get-Content` when `-Tail 0` and `-Wait` are used together (#20734) (Thanks @CarloToso!) -- Add `Aliases` to the properties shown up when formatting the help content of the parameter returned by `Get-Help` (#20994) -- Add implicit localization fallback to `Import-LocalizedData` (#19896) (Thanks @chrisdent-de!) -- Change `Test-FileCatalog` to use `File.OpenRead` to better handle the case where the file is being used (#20939) (Thanks @dxk3355!) -- Added `-Module` completion for `Save-Help` and `Update-Help` commands (#20678) (Thanks @ArmaanMcleod!) -- Add argument completer to `-Verb` for `Start-Process` (#20415) (Thanks @ArmaanMcleod!) -- Add argument completer to `-Scope` for `*-Variable`, `*-Alias` & `*-PSDrive` commands (#20451) (Thanks @ArmaanMcleod!) -- Add argument completer to `-Verb` for `Get-Verb` and `Get-Command` (#20286) (Thanks @ArmaanMcleod!) -- Fixing incorrect formatting string in `CommandSearcher` trace logging (#20928) (Thanks @powercode!) -- Ensure the filename is not null when logging WDAC ETW events (#20910) (Thanks @jborean93!) -- Fix four regressions introduced by the WDAC logging feature (#20913) -- Leave the input, output, and error handles unset when they are not redirected (#20853) -- Fix `Start-Process -PassThru` to make sure the `ExitCode` property is accessible for the returned `Process` object (#20749) (Thanks @CodeCyclone!) -- Fix `Group-Object` output using interpolated strings (#20745) (Thanks @mawosoft!) -- Fix rendering of `DisplayRoot` for network `PSDrive` (#20793) -- Fix `Invoke-WebRequest` to report correct size when `-Resume` is specified (#20207) (Thanks @LNKLEO!) -- Add `PSAdapter` and `ConsoleGuiTools` to module load telemetry allow list (#20641) -- Fix Web Cmdlets to allow `WinForm` apps to work correctly (#20606) -- Block getting help from network locations in restricted remoting sessions (#20593) -- Fix `Group-Object` to use current culture for its output (#20608) -- Add argument completer to `-Version` for `Set-StrictMode` (#20554) (Thanks @ArmaanMcleod!) -- Fix `Copy-Item` progress to only show completed when all files are copied (#20517) -- Fix UNC path completion regression (#20419) (Thanks @MartinGC94!) -- Add telemetry to check for specific tags when importing a module (#20371) -- Report error if invalid `-ExecutionPolicy` is passed to `pwsh` (#20460) -- Add `HelpUri` to `Remove-Service` (#20476) -- Fix `unixmode` to handle `setuid` and `sticky` when file is not an executable (#20366) -- Fix `Test-Connection` due to .NET 8 changes (#20369) -- Fix implicit remoting proxy cmdlets to act on common parameters (#20367) -- Set experimental features to stable for 7.4 release (#20285) -- Revert changes to continue using `BinaryFormatter` for `Out-GridView` (#20300) -- Fix `Get-Service` non-terminating error message to include category (#20276) -- Prevent `Export-CSV` from flushing with every input (#20282) (Thanks @Chris--A!) -- Fix a regression in DSC (#20268) -- Include the module version in error messages when module is not found (#20144) (Thanks @ArmaanMcleod!) -- Add `-Empty` and `-InputObject` parameters to `New-Guid` (#20014) (Thanks @CarloToso!) -- Remove the comment trigger from feedback provider (#20136) -- Prevent fallback to file completion when tab completing type names (#20084) (Thanks @MartinGC94!) -- Add the alias `r` to the parameter `-Recurse` for the `Get-ChildItem` command (#20100) (Thanks @kilasuit!) - -### Code Cleanup - -
    - - - -

    We thank the following contributors!

    -

    @eltociear, @ImportTaste, @ThomasNieto, @0o001

    - -
    - -
      -
    • Fix typos in the code base (#20147, #20492, #20632, #21015, #20838) (Thanks @eltociear!)
    • -
    • Add the missing alias LP to -LiteralPath for some cmdlets (#20820) (Thanks @ImportTaste!)
    • -
    • Remove parenthesis for empty attribute parameters (#20087) (Thanks @ThomasNieto!)
    • -
    • Add space around keyword according to the CodeFactor rule (#20090) (Thanks @ThomasNieto!)
    • -
    • Remove blank lines as instructed by CodeFactor rules (#20086) (Thanks @ThomasNieto!)
    • -
    • Remove trailing whitespace (#20085) (Thanks @ThomasNieto!)
    • -
    • Fix typo in error message (#20145) (Thanks @0o001!)
    • -
    - -
    - -### Tools - -- Make sure feedback link in the bot's comment is clickable (#20878) (Thanks @floh96!) -- Fix bot so anyone who comments will remove the "Resolution-No Activity" label (#20788) -- Fix bot configuration to prevent multiple comments about "no activity" (#20758) -- Add bot logic for closing GitHub issues after 6 months of "no activity" (#20525) -- Refactor bot for easier use and updating (#20805) -- Configure bot to add survey comment for closed issues (#20397) - -### Tests - -- Suppress error output from `Set-Location` tests (#20499) -- Fix typo in `FileCatalog.Tests.ps1` (#20329) (Thanks @eltociear!) -- Continue to improve tests for release automation (#20182) -- Skip the test on x86 as `InstallDate` is not visible on `Wow64` (#20165) -- Harden some problematic release tests (#20155) - -### Build and Packaging Improvements - -
    - - - -

    We thank the following contributors!

    -

    @alerickson, @Zhoneym, @0o001

    - -
    - -
      -
    • Bump .NET SDK to 8.0.101 (#21084)
    • -
    • Update the cgmanifest (#20083, #20436, #20523, #20560, #20627, #20764, #20906, #20933, #20955, #21047)
    • -
    • Update to the latest NOTICES file (#20074, #20161, #20385, #20453, #20576, #20590, #20880, #20905)
    • -
    • Bump StyleCop.Analyzers from 1.2.0-beta.507 to 1.2.0-beta.556 (#20953)
    • -
    • Bump xUnit to 2.6.6 (#21071)
    • -
    • Bump JsonSchema.Net to 5.5.0 (#21027)
    • -
    • Fix failures in GitHub action markdown-link-check (#20996)
    • -
    • Bump xunit.runner.visualstudio to 2.5.6 (#20966)
    • -
    • Bump github/codeql-action from 2 to 3 (#20927)
    • -
    • Bump Markdig.Signed to 0.34.0 (#20926)
    • -
    • Bump Microsoft.ApplicationInsights from 2.21.0 to 2.22.0 (#20888)
    • -
    • Bump Microsoft.NET.Test.Sdk to 17.8.0 (#20660)
    • -
    • Update apiscan.yml to have access to the AzDevOpsArtifacts variable group (#20671)
    • -
    • Set the ollForwardOnNoCandidateFx in runtimeconfig.json to roll forward only on minor and patch versions (#20689)
    • -
    • Sign the global tool shim executable (#20794)
    • -
    • Bump actions/github-script from 6 to 7 (#20682)
    • -
    • Remove RHEL7 publishing to packages.microsoft.com as it's no longer supported (#20849)
    • -
    • Bump Microsoft.CodeAnalysis.CSharp to 4.8.0 (#20751)
    • -
    • Add internal nuget feed to compliance build (#20669)
    • -
    • Copy azure blob with PowerShell global tool to private blob and move to CDN during release (#20659)
    • -
    • Fix release build by making the internal SDK parameter optional (#20658)
    • -
    • Update PSResourceGet version to 1.0.1 (#20652)
    • -
    • Make internal .NET SDK URL as a parameter for release builld (#20655)
    • -
    • Fix setting of variable to consume internal SDK source (#20644)
    • -
    • Bump Microsoft.Management.Infrastructure to v3.0.0 (#20642)
    • -
    • Bump Microsoft.PowerShell.Native to v7.4.0 (#20617)
    • -
    • Bump Microsoft.Security.Extensions from 1.2.0 to 1.3.0 (#20556)
    • -
    • Fix package version for .NET nuget packages (#20551)
    • -
    • Add SBOM for release pipeline (#20519)
    • -
    • Block any preview vPack release (#20243)
    • -
    • Only registry App Path for release package (#20478)
    • -
    • Increase timeout when publishing packages to pacakages.microsoft.com (#20470)
    • -
    • Fix alpine tar package name and do not crossgen alpine fxdependent package (#20459)
    • -
    • Bump PSReadLine from 2.2.6 to 2.3.4 (#20305)
    • -
    • Remove the ref folder before running compliance (#20373)
    • -
    • Updates RIDs used to generate component Inventory (#20370)
    • -
    • Bump XunitXml.TestLogger from 3.1.11 to 3.1.17 (#20293)
    • -
    • Update experimental-feature json files (#20335)
    • -
    • Use fxdependent-win-desktop runtime for compliance runs (#20326)
    • -
    • Release build: Change the names of the PATs (#20307)
    • -
    • Add mapping for mariner arm64 stable (#20213)
    • -
    • Put the calls to Set-AzDoProjectInfo and Set-AzDoAuthToken in the right order (#20306)
    • -
    • Enable vPack provenance data (#20220)
    • -
    • Bump actions/checkout from 3 to 4 (#20205)
    • -
    • Start using new packages.microsoft.com cli (#20140, #20141)
    • -
    • Add mariner arm64 to PMC release (#20176)
    • -
    • Fix typo donet to dotnet in build scripts and pipelines (#20122) (Thanks @0o001!)
    • -
    • Install the pmc cli
    • -
    • Add skip publish parameter
    • -
    • Add verbose to clone
    • -
    - -
    - -### Documentation and Help Content - -- Include information about upgrading in readme (#20993) -- Expand "iff" to "if-and-only-if" in XML doc content (#20852) -- Update LTS links in README.md to point to the v7.4 packages (#20839) (Thanks @kilasuit!) -- Update `README.md` to improve readability (#20553) (Thanks @AnkitaSikdar005!) -- Fix link in `docs/community/governance.md` (#20515) (Thanks @suravshresth!) -- Update `ADOPTERS.md` (#20555) (Thanks @AnkitaSikdar005!) -- Fix a typo in `ADOPTERS.md` (#20504, #20520) (Thanks @shruti-sen2004!) -- Correct grammatical errors in `README.md` (#20509) (Thanks @alienishi!) -- Add 7.3 changelog URL to readme (#20473) (Thanks @Saibamen!) -- Clarify some comments and documentation (#20462) (Thanks @darkstar!) - -[7.5.0-preview.1]: https://github.com/PowerShell/PowerShell/compare/v7.4.1...v7.5.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 472b5958a8c..f8288b53b67 100644 --- a/DotnetRuntimeMetadata.json +++ b/DotnetRuntimeMetadata.json @@ -4,7 +4,7 @@ "quality": "daily", "qualityFallback": "preview", "packageVersionPattern": "9.0.0-preview.6", - "sdkImageVersion": "9.0.100", + "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 b73b3e60a7b..d71432aed2f 100644 --- a/PowerShell.Common.props +++ b/PowerShell.Common.props @@ -58,6 +58,11 @@ $(ReleaseTagVersionPart).$(ReleaseTagSemVersionPart) $(ReleaseTagVersionPart).$(GAIncrementValue) + + $(PSCoreFileVersion) + $([System.Version]::Parse($(PSCoreFileVersion)).Major).$([System.Version]::Parse($(PSCoreFileVersion)).Minor).0.$([System.Version]::Parse($(PSCoreFileVersion)).Revision) @@ -84,7 +89,7 @@ --> $(PSCoreFileVersion) @@ -139,8 +144,8 @@ (c) Microsoft Corporation. PowerShell 7 - net9.0 - 11.0 + net11.0 + preview true true @@ -158,7 +163,7 @@ $(DefineConstants);CORECLR - true + true @@ -171,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 a6058f16ead..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.37.0 - BSD-2-Clause +Markdig.Signed 0.45.0 - BSD-2-Clause @@ -55,14 +55,14 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -Json.More.Net 2.0.1.2 - MIT +Json.More.Net 2.1.1 - MIT -Copyright (c) 2024 Greg Dennis +Copyright (c) .NET Foundation and Contributors MIT License -Copyright (c) 2024 Greg Dennis +Copyright (c) .NET Foundation and Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -87,14 +87,14 @@ SOFTWARE. --------------------------------------------------------- -JsonPointer.Net 5.0.0 - MIT +JsonPointer.Net 5.3.1 - MIT -Copyright (c) 2024 Greg Dennis +Copyright (c) .NET Foundation and Contributors MIT License -Copyright (c) 2024 Greg Dennis +Copyright (c) .NET Foundation and Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -119,25 +119,39 @@ SOFTWARE. --------------------------------------------------------- -JsonSchema.Net 7.0.1 - MIT +JsonSchema.Net 7.4.0 - MIT +Copyright (c) .NET Foundation and Contributors MIT License -Copyright (c) +Copyright (c) .NET Foundation and Contributors -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. --------------------------------------------------------- --------------------------------------------------------- -Microsoft.ApplicationInsights 2.22.0 - MIT +Microsoft.ApplicationInsights 2.23.0 - MIT (c) Microsoft Corporation @@ -156,19 +170,22 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -Microsoft.Bcl.AsyncInterfaces 8.0.0 - MIT +Microsoft.Bcl.AsyncInterfaces 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 @@ -176,73 +193,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. --------------------------------------------------------- --------------------------------------------------------- -Microsoft.CodeAnalysis.Common 4.9.2 - MIT +Microsoft.CodeAnalysis.Common 5.0.0 - MIT (c) Microsoft Corporation @@ -262,13 +264,16 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -Microsoft.CodeAnalysis.CSharp 4.9.2 - MIT +Microsoft.CodeAnalysis.CSharp 5.0.0 - MIT (c) Microsoft Corporation Copyright (c) Microsoft Corporation +ACopyright (c) Microsoft Corporation +CCopyright (c) Microsoft Corporation +DCopyright (c) Microsoft Corporation +OCopyright (c) Microsoft Corporation Copyright (c) .NET Foundation and Contributors -Copyright (c) Microsoft Corporation. Alle Rechte MIT License @@ -284,16 +289,16 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -Microsoft.Extensions.ObjectPool 8.0.4 - MIT +Microsoft.Extensions.ObjectPool 10.0.3 - MIT -Copyright 2019 The gRPC Copyright Jorn Zaefferer (c) Microsoft Corporation Copyright (c) Andrew Arnott Copyright (c) 2015, Google Inc. Copyright (c) 2019 David Fowler Copyright (c) HTML5 Boilerplate +Copyright 2019 The gRPC Authors Copyright (c) 2016 Richard Morris Copyright (c) 1998 John D. Polstra Copyright (c) 2017 Yoshifumi Kawai @@ -354,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 @@ -374,72 +379,22 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI --------------------------------------------------------- -Microsoft.Win32.Registry 4.7.0 - MIT - - -(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 - -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 8.0.0 - MIT +Microsoft.Win32.Registry.AccessControl 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 @@ -447,85 +402,73 @@ 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. --------------------------------------------------------- --------------------------------------------------------- -Microsoft.Win32.SystemEvents 8.0.0 - MIT +Microsoft.Win32.SystemEvents 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 @@ -533,73 +476,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. --------------------------------------------------------- --------------------------------------------------------- -Microsoft.Windows.Compatibility 8.0.8 - MIT +Microsoft.Windows.Compatibility 10.0.3 - MIT (c) Microsoft Corporation @@ -618,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 @@ -652,20 +580,21 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- -runtime.android-arm.runtime.native.System.IO.Ports 9.0.0-preview.3.24172.9 - MIT +runtime.android-arm.runtime.native.System.IO.Ports 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 @@ -674,87 +603,72 @@ 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) 2015-2018, 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 +(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. --------------------------------------------------------- --------------------------------------------------------- -runtime.android-arm64.runtime.native.System.IO.Ports 9.0.0-preview.3.24172.9 - MIT +runtime.android-arm64.runtime.native.System.IO.Ports 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 @@ -763,87 +677,72 @@ 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) 2015-2018, 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 +(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. --------------------------------------------------------- --------------------------------------------------------- -runtime.android-x64.runtime.native.System.IO.Ports 9.0.0-preview.3.24172.9 - MIT +runtime.android-x64.runtime.native.System.IO.Ports 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 @@ -852,87 +751,72 @@ 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) 2015-2018, 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 +(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. --------------------------------------------------------- --------------------------------------------------------- -runtime.android-x86.runtime.native.System.IO.Ports 9.0.0-preview.3.24172.9 - MIT +runtime.android-x86.runtime.native.System.IO.Ports 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 @@ -941,86 +825,73 @@ 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) 2015-2018, 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 +(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) +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: - -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 8.0.0 - MIT +runtime.linux-arm.runtime.native.System.IO.Ports 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 @@ -1028,85 +899,73 @@ 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. --------------------------------------------------------- --------------------------------------------------------- -runtime.linux-arm64.runtime.native.System.IO.Ports 8.0.0 - MIT +runtime.linux-arm64.runtime.native.System.IO.Ports 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 @@ -1114,86 +973,72 @@ 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. --------------------------------------------------------- --------------------------------------------------------- -runtime.linux-bionic-arm64.runtime.native.System.IO.Ports 9.0.0-preview.3.24172.9 - MIT +runtime.linux-bionic-arm64.runtime.native.System.IO.Ports 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 @@ -1202,87 +1047,72 @@ 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) 2015-2018, 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 +(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. --------------------------------------------------------- --------------------------------------------------------- -runtime.linux-bionic-x64.runtime.native.System.IO.Ports 9.0.0-preview.3.24172.9 - MIT +runtime.linux-bionic-x64.runtime.native.System.IO.Ports 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 @@ -1291,87 +1121,72 @@ 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) 2015-2018, 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 +(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. --------------------------------------------------------- --------------------------------------------------------- -runtime.linux-musl-arm.runtime.native.System.IO.Ports 9.0.0-preview.3.24172.9 - MIT +runtime.linux-musl-arm.runtime.native.System.IO.Ports 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 @@ -1380,87 +1195,72 @@ 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) 2015-2018, 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 +(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. --------------------------------------------------------- --------------------------------------------------------- -runtime.linux-musl-arm64.runtime.native.System.IO.Ports 9.0.0-preview.3.24172.9 - MIT +runtime.linux-musl-arm64.runtime.native.System.IO.Ports 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 @@ -1469,87 +1269,72 @@ 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) 2015-2018, 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 +(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. --------------------------------------------------------- --------------------------------------------------------- -runtime.linux-musl-x64.runtime.native.System.IO.Ports 9.0.0-preview.3.24172.9 - MIT +runtime.linux-musl-x64.runtime.native.System.IO.Ports 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 @@ -1558,86 +1343,73 @@ 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) 2015-2018, 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 +(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. --------------------------------------------------------- --------------------------------------------------------- -runtime.linux-x64.runtime.native.System.IO.Ports 8.0.0 - MIT +runtime.linux-x64.runtime.native.System.IO.Ports 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 @@ -1645,86 +1417,72 @@ 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. --------------------------------------------------------- --------------------------------------------------------- -runtime.maccatalyst-arm64.runtime.native.System.IO.Ports 9.0.0-preview.3.24172.9 - MIT +runtime.maccatalyst-arm64.runtime.native.System.IO.Ports 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 @@ -1733,87 +1491,72 @@ 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) 2015-2018, 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 +(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. --------------------------------------------------------- --------------------------------------------------------- -runtime.maccatalyst-x64.runtime.native.System.IO.Ports 9.0.0-preview.3.24172.9 - MIT +runtime.maccatalyst-x64.runtime.native.System.IO.Ports 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 @@ -1822,96 +1565,72 @@ 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) 2015-2018, 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 +(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. --------------------------------------------------------- --------------------------------------------------------- -runtime.native.System.Data.SqlClient.sni 4.7.0 - MIT +runtime.native.System.Data.SqlClient.sni 4.4.0 - MIT -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. -Copyright (c) 2011, Google Inc. -(c) 1997-2005 Sean Eron Anderson. -Copyright (c) 2007 James Newton-King +(c) 2022 GitHub, Inc. +(c) Microsoft Corporation +(c) 1997-2005 Sean Eron Anderson 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 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers The MIT License (MIT) @@ -1942,19 +1661,22 @@ SOFTWARE. --------------------------------------------------------- -runtime.native.System.IO.Ports 8.0.0 - MIT +runtime.native.System.IO.Ports 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 @@ -1962,85 +1684,73 @@ 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. --------------------------------------------------------- --------------------------------------------------------- -runtime.osx-arm64.runtime.native.System.IO.Ports 8.0.0 - MIT +runtime.osx-arm64.runtime.native.System.IO.Ports 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 @@ -2048,85 +1758,73 @@ 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. --------------------------------------------------------- --------------------------------------------------------- -runtime.osx-x64.runtime.native.System.IO.Ports 8.0.0 - MIT +runtime.osx-x64.runtime.native.System.IO.Ports 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 @@ -2134,85 +1832,73 @@ 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.CodeDom 8.0.0 - MIT +System.CodeDom 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 @@ -2220,85 +1906,73 @@ 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.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 @@ -2306,85 +1980,73 @@ 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 8.0.0 - MIT +System.ComponentModel.Composition.Registration 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 @@ -2392,85 +2054,73 @@ 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.Registration 8.0.0 - MIT +System.Configuration.ConfigurationManager 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 @@ -2478,85 +2128,73 @@ 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.Configuration.ConfigurationManager 8.0.0 - MIT +System.Data.Odbc 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 @@ -2564,85 +2202,73 @@ 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.Data.Odbc 8.0.0 - MIT +System.Data.OleDb 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 @@ -2650,85 +2276,92 @@ 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) +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 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.Data.SqlClient 4.9.0 - MIT + + +(c) Microsoft Corporation + +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.Data.OleDb 8.0.0 - MIT +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) 1998 Microsoft. To +Copyright (c) 2015 Andrew Gallant Copyright (c) 2022, Wojciech Mula Copyright (c) 2017 Yoshifumi Kawai Copyright (c) 2022, Geoff Langdale @@ -2736,138 +2369,73 @@ 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. - -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.8.6 - MIT - - -(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 -Copyright (c) 2015 The Chromium Authors -Portions (c) International Organization -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 - -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.DiagnosticSource 8.0.1 - MIT +System.Diagnostics.PerformanceCounter 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 @@ -2875,85 +2443,73 @@ 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.Diagnostics.EventLog 8.0.0 - MIT +System.DirectoryServices 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 @@ -2961,85 +2517,73 @@ 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.Diagnostics.PerformanceCounter 8.0.0 - MIT +System.DirectoryServices.AccountManagement 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 @@ -3047,85 +2591,73 @@ 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) +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: - -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 8.0.0 - MIT +System.DirectoryServices.Protocols 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 @@ -3133,43 +2665,64 @@ 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 +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 @@ -3194,24 +2747,26 @@ 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 8.0.0 - MIT +System.IO.Packaging 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 @@ -3219,85 +2774,73 @@ 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.DirectoryServices.Protocols 8.0.0 - MIT +System.IO.Ports 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 @@ -3305,120 +2848,73 @@ 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. - -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 8.0.8 - 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.Formats.Asn1 8.0.1 - MIT +System.Management 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 @@ -3426,85 +2922,73 @@ 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.IO.Packaging 8.0.0 - MIT +System.Net.Http.WinHttpHandler 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 @@ -3512,85 +2996,73 @@ 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.IO.Ports 8.0.0 - MIT +System.Reflection.Context 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 @@ -3598,85 +3070,73 @@ 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.Management 8.0.0 - MIT +System.Runtime.Caching 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 @@ -3684,85 +3144,73 @@ 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) +MIT License -Copyright (c) .NET Foundation and Contributors +Copyright (c) -All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -System.Net.Http.WinHttpHandler 8.0.2 - MIT +System.Security.Cryptography.Pkcs 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 @@ -3770,170 +3218,73 @@ 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. - -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.Numerics.Vectors 4.5.0 - MIT - - -(c) 2023 GitHub, Inc. -(c) Microsoft Corporation -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1991-2017 Unicode, Inc. -Copyright (c) 2015 The Chromium Authors -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -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 - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Private.ServiceModel 4.10.3 - MIT - - -(c) Microsoft Corporation -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -System.Reflection.Context 8.0.0 - MIT +System.Security.Cryptography.ProtectedData 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 @@ -3941,139 +3292,73 @@ 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. - -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 - - -(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 - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. +MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Copyright (c) -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --------------------------------------------------------- --------------------------------------------------------- -System.Reflection.Metadata 8.0.0 - MIT +System.Security.Cryptography.Xml 10.0.3 - MIT +Copyright (c) 2021 Copyright (c) Six Labors -Gets the Copyright Table (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 @@ -4081,85 +3366,73 @@ 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.Runtime.Caching 8.0.0 - MIT +System.Security.Permissions 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 @@ -4167,748 +3440,63 @@ 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. - -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: +MIT License -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +Copyright (c) -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.CompilerServices.Unsafe 6.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) 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-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. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -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 8.0.0 - MIT - - -Copyright (c) Six Labors -(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.Security.Cryptography.ProtectedData 8.0.0 - MIT - - -Copyright (c) Six Labors -(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.Security.Cryptography.Xml 8.0.1 - MIT - - -Copyright (c) Six Labors -(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.Security.Permissions 8.0.0 - MIT - - -Copyright (c) Six Labors -(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.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. +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 +System.ServiceModel.Http 10.0.652802 - MIT (c) Microsoft Corporation Copyright (c) .NET Foundation and Contributors -Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) +Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) Provided The MIT License (MIT) @@ -4939,12 +3527,12 @@ SOFTWARE. --------------------------------------------------------- -System.ServiceModel.Security 4.10.3 - MIT +System.ServiceModel.NetFramingBase 10.0.652802 - MIT (c) Microsoft Corporation Copyright (c) .NET Foundation and Contributors -Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) +Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) Provided The MIT License (MIT) @@ -4975,62 +3563,12 @@ SOFTWARE. --------------------------------------------------------- -System.ServiceModel.Syndication 8.0.0 - MIT +System.ServiceModel.NetTcp 10.0.652802 - MIT -Copyright (c) Six Labors (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 +Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) Provided The MIT License (MIT) @@ -5061,62 +3599,12 @@ SOFTWARE. --------------------------------------------------------- -System.ServiceProcess.ServiceController 8.0.0 - MIT +System.ServiceModel.Primitives 10.0.652802 - MIT -Copyright (c) Six Labors (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 +Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) Provided The MIT License (MIT) @@ -5147,19 +3635,22 @@ SOFTWARE. --------------------------------------------------------- -System.Speech 8.0.0 - MIT +System.ServiceModel.Syndication 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 @@ -5167,118 +3658,73 @@ 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. - -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.Text.Encoding.CodePages 8.0.0 - MIT - - - -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 8.0.0 - MIT +System.ServiceProcess.ServiceController 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 @@ -5286,85 +3732,73 @@ 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.Threading.AccessControl 8.0.0 - MIT +System.Speech 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 @@ -5372,75 +3806,63 @@ 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.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) @@ -5471,19 +3893,22 @@ SOFTWARE. --------------------------------------------------------- -System.Windows.Extensions 8.0.0 - MIT +System.Windows.Extensions 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 @@ -5491,67 +3916,52 @@ 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. --------------------------------------------------------- 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/manpage/pwsh.1.ronn b/assets/manpage/pwsh.1.ronn index 13126164551..98320cc60c8 100644 --- a/assets/manpage/pwsh.1.ronn +++ b/assets/manpage/pwsh.1.ronn @@ -40,7 +40,7 @@ Some parameters have abbreviated forms * `-Command` | `-c`: Executes the specified commands (and any parameters) as though they were typed at the PowerShell command prompt, and then exits, unless NoExit is - specified. The value of Command can be `-`, a string or a script block. If + specified. The value of Command can be `-`, a string, or a script block. If the value of Command is `-`, the command text is read from standard input. If the value of Command is a script block, the script block must be enclosed in braces (`{}`). You can specify a script block only when running PowerShell in @@ -171,7 +171,7 @@ Some parameters have abbreviated forms * `-WindowStyle` | `-w` Sets the window style for the session. Valid values are Normal, Minimized, - Maximized and Hidden. This parameter only applies to Windows. Using this + Maximized, and Hidden. This parameter only applies to Windows. Using this parameter on non-Windows platforms results in an error. * `-WorkingDirectory` | `-wd` | `-wo` 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 e21194c9af9..d353eb251e5 100644 --- a/build.psm1 +++ b/build.psm1 @@ -2,7 +2,7 @@ # Licensed under the MIT License. param( - # Skips a check that prevents building PowerShell on unsupported Linux distributions + # Skips a check that prevents building PowerShell on unsupported Linux distributions. [parameter(Mandatory = $false)][switch]$SkipLinuxDistroCheck = $false ) @@ -11,7 +11,7 @@ param( # CI runs with PowerShell 5.0, so don't use features like ?: && || Set-StrictMode -Version 3.0 -# On Unix paths is separated by colon +# On Unix paths is separated by colon. # On Windows paths is separated by semicolon $script:TestModulePathSeparator = [System.IO.Path]::PathSeparator $script:Options = $null @@ -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')] @@ -770,7 +911,7 @@ function Switch-PSNugetConfig { } elseif ( $Source -eq 'NuGetOnly') { New-NugetConfigFile -NugetPackageSource $nugetorg -Destination "$PSScriptRoot/" @extraParams New-NugetConfigFile -NugetPackageSource $gallery -Destination "$PSScriptRoot/src/Modules/" @extraParams - New-NugetConfigFile -NugetPackageSource $gallery -Destination "$PSScriptRoot/test/tools/Modules/" @extraParams + New-NugetConfigFile -NugetPackageSource $gallery -Destination "$PSScriptRoot/test/tools/Modules/" @extraParams } elseif ( $Source -eq 'Private') { $powerShellPackages = [NugetPackageSource] @{Url = 'https://pkgs.dev.azure.com/powershell/PowerShell/_packaging/PowerShell/nuget/v3/index.json'; Name = 'powershell' } @@ -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("net9.0")] - [string]$Framework = "net9.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] @@ -1758,11 +2124,32 @@ function Publish-TestResults $resolvedPath = (Resolve-Path -Path $Path).ProviderPath Write-Host "##vso[artifact.upload containerfolder=testResults;artifactname=testResults]$resolvedPath" + } elseif ($env:GITHUB_WORKFLOW -and $env:RUNNER_WORKSPACE) { + # In GitHub Actions + $destinationPath = Join-Path -Path $env:RUNNER_WORKSPACE -ChildPath 'testResults' + + # Create the folder if it does not exist + if (!(Test-Path -Path $destinationPath)) { + $null = New-Item -ItemType Directory -Path $destinationPath -Force + } + + Copy-Item -Path $Path -Destination $destinationPath -Force -Verbose } } 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 @@ -1773,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" } @@ -1783,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)] @@ -1810,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()] @@ -1855,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" @@ -1872,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')] @@ -1897,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] ) { @@ -1911,6 +2420,8 @@ function Test-PSPesterResults { Show-PSPesterError -testFailure $testfail } + + Write-LogGroupEnd -Title 'TEST FAILURES' throw "$($x.'test-results'.failures) tests in $TestArea failed" } } @@ -1931,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" } @@ -1943,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 @@ -2002,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 @@ -2013,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, @@ -2025,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 @@ -2123,7 +2694,6 @@ function Install-Dotnet { $installArgs += @{ SkipNonVersionedFiles = $true } $installArgs | Out-String | Write-Verbose -Verbose - & ./$installScript @installArgs } else { @@ -2160,66 +2730,69 @@ 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, # we currently pin dotnet-cli version, and will # update it when more stable version comes out. [string]$Version = $dotnetCLIRequiredVersion, - [switch]$Package, [switch]$NoSudo, [switch]$BuildLinuxArm, - [switch]$Force + [switch]$Force, + [Parameter(Mandatory = $true)] + # 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" ) Write-Log -message "Installing PowerShell build dependencies" @@ -2232,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 } @@ -2252,7 +2827,9 @@ function Start-PSBootstrap { elseif ($environment.IsUbuntu18) { $Deps += "libicu60"} # Packaging tools - if ($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 @@ -2276,7 +2853,9 @@ function Start-PSBootstrap { $Deps += "libicu", "openssl-libs" # Packaging tools - if ($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 @@ -2297,7 +2876,8 @@ function Start-PSBootstrap { $Deps += "wget" # Packaging tools - if ($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" @@ -2336,55 +2916,86 @@ function Start-PSBootstrap { } } - # Install [fpm](https://github.com/jordansissel/fpm) - if ($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" } - Write-Verbose -Verbose "Calling Find-Dotnet from Start-PSBootstrap" + if ($Scenario -in 'All', 'Both', 'DotNet') { + Write-LogGroupStart -Title "Install .NET SDK" - # Try to locate dotnet-SDK before installing it - Find-Dotnet + Write-Verbose -Verbose "Calling Find-Dotnet from Start-PSBootstrap" - Write-Verbose -Verbose "Back from calling Find-Dotnet from Start-PSBootstrap" + # Try to locate dotnet-SDK before installing it + Find-Dotnet - # Install dotnet-SDK - $dotNetExists = precheck 'dotnet' $null - $dotNetVersion = [string]::Empty - if($dotNetExists) { - $dotNetVersion = Find-RequiredSDK $dotnetCLIRequiredVersion - } + Write-Verbose -Verbose "Back from calling Find-Dotnet from Start-PSBootstrap" - if(!$dotNetExists -or $dotNetVersion -ne $dotnetCLIRequiredVersion -or $Force.IsPresent) { - if($Force.IsPresent) { - Write-Log -message "Installing dotnet due to -Force." - } - elseif(!$dotNetExists) { - Write-Log -message "dotnet not present. Installing dotnet." - } - else { - Write-Log -message "dotnet out of date ($dotNetVersion). Updating dotnet." + # Install dotnet-SDK + $dotNetExists = precheck 'dotnet' $null + $dotNetVersion = [string]::Empty + if($dotNetExists) { + $dotNetVersion = Find-RequiredSDK $dotnetCLIRequiredVersion } - $DotnetArguments = @{ Channel=$Channel; Version=$Version; NoSudo=$NoSudo } + if(!$dotNetExists -or $dotNetVersion -ne $dotnetCLIRequiredVersion -or $Force.IsPresent) { + if($Force.IsPresent) { + Write-Log -message "Installing dotnet due to -Force." + } + elseif(!$dotNetExists) { + Write-Log -message "dotnet not present. Installing dotnet." + } + else { + Write-Log -message "dotnet out of date ($dotNetVersion). Updating dotnet." + } - if ($dotnetAzureFeed) { - $null = $DotnetArguments.Add("AzureFeed", $dotnetAzureFeed) - $null = $DotnetArguments.Add("FeedCredential", $dotnetAzureFeedSecret) - } + $DotnetArguments = @{ Channel=$Channel; Version=$Version; NoSudo=$NoSudo } - Install-Dotnet @DotnetArguments - } - else { - Write-Log -message "dotnet is already installed. Skipping installation." + if ($dotnetAzureFeed) { + $null = $DotnetArguments.Add("AzureFeed", $dotnetAzureFeed) + $null = $DotnetArguments.Add("FeedCredential", $dotnetAzureFeedSecret) + } + + Install-Dotnet @DotnetArguments + } + 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)) { @@ -2392,17 +3003,32 @@ function Start-PSBootstrap { $psInstallFile = [System.IO.Path]::Combine($PSScriptRoot, "tools", "install-powershell.ps1") & $psInstallFile -AddToPath } - if ($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 @@ -2412,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 @@ -2436,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 = @(), @@ -2503,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 ( @@ -2513,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 } @@ -2552,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() @@ -2566,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 ) @@ -2596,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 + } + } } <# @@ -2648,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" @@ -2667,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)] @@ -2677,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 { @@ -2686,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)) @@ -2702,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() @@ -2714,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)] @@ -2773,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)] @@ -2814,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)] @@ -2821,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 { @@ -3182,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()] @@ -3199,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] @@ -3231,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, @@ -3401,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 ) @@ -3427,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)] @@ -3503,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 @@ -3513,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')] @@ -3583,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) { @@ -3604,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 @@ -3637,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) { @@ -3652,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 ) @@ -3686,7 +4773,7 @@ function Clear-NativeDependencies $filesToDeleteWinDesktop = @() $deps = Get-Content "$PublishFolder/pwsh.deps.json" -Raw | ConvertFrom-Json -Depth 20 - $targetRuntime = ".NETCoreApp,Version=v9.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*' @@ -3718,13 +4805,21 @@ 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 $versionParts = $oldVersion -split '\.' $channel = $versionParts[0], $versionParts[1] -join '.' Write-Verbose "channel: $channel" -Verbose - $azure_feed = 'https://dotnetcli.azureedge.net/dotnet' + $azure_feed = 'https://builds.dotnet.microsoft.com/dotnet' $version_file_url = "$azure_feed/Sdk/$channel/latest.version" $version = Invoke-RestMethod $version_file_url Write-Verbose "updating from: $oldVersion to: $version" -Verbose @@ -3737,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 e50f54d3cf6..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 @@ -150,6 +150,7 @@ These modules include: * @jdhitsolutions * @TobiasPSP * @doctordns +* @kilasuit ## Security 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 65324522984..d31f5220d83 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "9.0.100" + "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 f18bdae611d..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>net9.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/CimIndicationWatcher.cs b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimIndicationWatcher.cs index eb69ba67392..899e67495cc 100644 --- a/src/Microsoft.Management.Infrastructure.CimCmdlets/CimIndicationWatcher.cs +++ b/src/Microsoft.Management.Infrastructure.CimCmdlets/CimIndicationWatcher.cs @@ -239,7 +239,7 @@ private void NewSubscriptionResultHandler(object src, CimSubscriptionEventArgs a /// If set EnableRaisingEvents to false, which will be ignored /// </para> /// </summary> - [BrowsableAttribute(false)] + [Browsable(false)] public bool EnableRaisingEvents { get 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 a25ce533fba..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="9.0.0" /> + <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 9b049c21d51..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="9.0.0" /> + <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 bdeef0dd7a0..79b1c862bfd 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/CombinePathCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/CombinePathCommand.cs @@ -32,7 +32,8 @@ public class JoinPathCommand : CoreCommandWithCredentialsBase [Parameter(Position = 1, Mandatory = true, ValueFromPipelineByPropertyName = true)] [AllowNull] [AllowEmptyString] - public string ChildPath { get; set; } + [AllowEmptyCollection] + public string[] ChildPath { get; set; } /// <summary> /// Gets or sets additional childPaths to the command. @@ -50,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 @@ -64,7 +80,15 @@ protected override void ProcessRecord() Path != null, "Since Path is a mandatory parameter, paths should never be null"); - string combinedChildPath = ChildPath; + string combinedChildPath = string.Empty; + + if (this.ChildPath != null) + { + foreach (string childPath in this.ChildPath) + { + combinedChildPath = SessionState.Path.Combine(combinedChildPath, childPath, CmdletProviderContext); + } + } // join the ChildPath elements if (AdditionalChildPath != null) @@ -119,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 2203c920c1e..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,54 +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; - } - - /// <summary> - /// System Restore APIs are not supported on the ARM platform. Skip the system restore operation is necessary. - /// </summary> - /// <param name="cmdlet"></param> - /// <returns></returns> - internal static bool SkipSystemRestoreOperationForARMPlatform(PSCmdlet cmdlet) - { - bool retValue = false; - if (PsUtils.IsRunningOnProcessorArchitectureARM()) - { - var ex = new InvalidOperationException(ComputerResources.SystemRestoreNotSupported); - var er = new ErrorRecord(ex, "SystemRestoreNotSupported", ErrorCategory.InvalidOperation, null); - cmdlet.WriteError(er); - retValue = true; - } - - return retValue; + 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/JobProcessCollection.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/JobProcessCollection.cs new file mode 100644 index 00000000000..78560543939 --- /dev/null +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/JobProcessCollection.cs @@ -0,0 +1,125 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable +#if !UNIX +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using Microsoft.Win32.SafeHandles; + +namespace Microsoft.PowerShell.Commands; + +/// <summary> +/// JobProcessCollection is a helper class used by Start-Process -Wait cmdlet to monitor the +/// child processes created by the main process hosted by the Start-process cmdlet. +/// </summary> +internal sealed class JobProcessCollection : IDisposable +{ + /// <summary> + /// Stores the initialisation state of the job and completion port. + /// </summary> + private bool? _initStatus; + + /// <summary> + /// JobObjectHandle is a reference to the job object used to track + /// the child processes created by the main process hosted by the Start-Process cmdlet. + /// </summary> + private Interop.Windows.SafeJobHandle? _jobObject; + + /// <summary> + /// The completion port handle that is used to monitor job events. + /// </summary> + private Interop.Windows.SafeIoCompletionPort? _completionPort; + + /// <summary> + /// Initializes a new instance of the <see cref="JobProcessCollection"/> class. + /// </summary> + public JobProcessCollection() + { } + + /// <summary> + /// Initializes the job and IO completion port and adds the process to the + /// job object. + /// </summary> + /// <param name="process">The process to add to the job.</param> + /// <returns>Whether the job creation and assignment worked or not.</returns> + public bool AssignProcessToJobObject(SafeProcessHandle process) + => InitializeJob() && Interop.Windows.AssignProcessToJobObject(_jobObject, process); + + /// <summary> + /// Blocks the current thread until all processes in the job have exited. + /// </summary> + /// <param name="cancellationToken">A token to cancel the operation.</param> + public void WaitForExit(CancellationToken cancellationToken) + { + if (_completionPort is null) + { + return; + } + + using var cancellationRegistration = cancellationToken.Register(() => + { + Interop.Windows.PostQueuedCompletionStatus( + _completionPort, + Interop.Windows.JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO); + }); + + int completionCode = 0; + do + { + Interop.Windows.GetQueuedCompletionStatus( + _completionPort, + Interop.Windows.INFINITE, + out completionCode); + } + while (completionCode != Interop.Windows.JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO); + cancellationToken.ThrowIfCancellationRequested(); + } + + [MemberNotNullWhen(true, [nameof(_jobObject), nameof(_completionPort)])] + private bool InitializeJob() + { + if (_initStatus.HasValue) + { + return _initStatus.Value; + } + + if (_jobObject is null) + { + _jobObject = Interop.Windows.CreateJobObject(); + if (_jobObject.IsInvalid) + { + _initStatus = false; + _jobObject.Dispose(); + _jobObject = null; + return false; + } + } + + if (_completionPort is null) + { + _completionPort = Interop.Windows.CreateIoCompletionPort(); + if (_completionPort.IsInvalid) + { + _initStatus = false; + _completionPort.Dispose(); + _completionPort = null; + return false; + } + } + + _initStatus = Interop.Windows.SetInformationJobObject( + _jobObject, + _completionPort); + + return _initStatus.Value; + } + + public void Dispose() + { + _jobObject?.Dispose(); + _completionPort?.Dispose(); + } +} +#endif diff --git a/src/Microsoft.PowerShell.Commands.Management/commands/management/Navigation.cs b/src/Microsoft.PowerShell.Commands.Management/commands/management/Navigation.cs index 17e88a0e116..6ab8f1d652d 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Navigation.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Navigation.cs @@ -25,7 +25,7 @@ public abstract class CoreCommandBase : PSCmdlet, IDynamicParameters /// An instance of the PSTraceSource class used for trace output /// using "NavigationCommands" as the category. /// </summary> - [Dbg.TraceSourceAttribute("NavigationCommands", "The namespace navigation tracer")] + [Dbg.TraceSource("NavigationCommands", "The namespace navigation tracer")] internal static readonly Dbg.PSTraceSource tracer = Dbg.PSTraceSource.GetTracer("NavigationCommands", "The namespace navigation tracer"); #endregion Tracer @@ -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 b2b9e6c1a85..fb134ce7a11 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/NewPropertyCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/NewPropertyCommand.cs @@ -1,7 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Management.Automation; +using System.Management.Automation.Language; namespace Microsoft.PowerShell.Commands { @@ -63,6 +67,9 @@ public string[] LiteralPath /// </summary> [Parameter(ValueFromPipelineByPropertyName = true)] [Alias("Type")] +#if !UNIX + [ArgumentCompleter(typeof(PropertyTypeArgumentCompleter))] +#endif public string PropertyType { get; set; } /// <summary> @@ -175,4 +182,114 @@ protected override void ProcessRecord() #endregion Command code } + +#if !UNIX + /// <summary> + /// Provides argument completion for PropertyType parameter. + /// </summary> + public class PropertyTypeArgumentCompleter : IArgumentCompleter + { + 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", + "Binary", + "DWord", + "MultiString", + "QWord", + "Unknown" + }; + + /// <summary> + /// Returns completion results for PropertyType 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) + => IsRegistryProvider(fakeBoundParameters) + ? CompletionHelpers.GetMatchingResults( + wordToComplete, + possibleCompletionValues: s_RegistryPropertyTypes, + displayInfoMapper: RegistryPropertyTypeDisplayInfoMapper, + resultType: CompletionResultType.ParameterValue) + : []; + + /// <summary> + /// Checks if parameter paths are from Registry provider. + /// </summary> + /// <param name="fakeBoundParameters">The fake bound parameters.</param> + /// <returns>Boolean indicating if paths are from Registry Provider.</returns> + private static bool IsRegistryProvider(IDictionary fakeBoundParameters) + { + Collection<PathInfo> paths; + + if (fakeBoundParameters.Contains("Path")) + { + paths = ResolvePath(fakeBoundParameters["Path"], isLiteralPath: false); + } + else if (fakeBoundParameters.Contains("LiteralPath")) + { + paths = ResolvePath(fakeBoundParameters["LiteralPath"], isLiteralPath: true); + } + else + { + paths = ResolvePath(@".\", isLiteralPath: false); + } + + return paths.Count > 0 && paths[0].Provider.NameEquals("Registry"); + } + + /// <summary> + /// Resolve path or literal path using Resolve-Path. + /// </summary> + /// <param name="path">The path to resolve.</param> + /// <param name="isLiteralPath">Specifies if path is literal path.</param> + /// <returns>Collection of Pathinfo objects.</returns> + private static Collection<PathInfo> ResolvePath(object path, bool isLiteralPath) + { + using var ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace); + + ps.AddCommand("Microsoft.PowerShell.Management\\Resolve-Path"); + ps.AddParameter(isLiteralPath ? "LiteralPath" : "Path", path); + + Collection<PathInfo> output = ps.Invoke<PathInfo>(); + + return output; + } + } +#endif } 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 73f00b5acc6..91efda12263 100644 --- a/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs +++ b/src/Microsoft.PowerShell.Commands.Management/commands/management/Process.cs @@ -16,7 +16,6 @@ using System.Net; using System.Runtime.InteropServices; using System.Runtime.Serialization; -using System.Security; using System.Security.Principal; using System.Text; using System.Threading; @@ -1604,7 +1603,7 @@ private static string MapReturnCodeToErrorMessage(int returnCode) [OutputType(typeof(Process))] public sealed class StartProcessCommand : PSCmdlet, IDisposable { - private ManualResetEvent _waithandle = null; + private readonly CancellationTokenSource _cancellationTokenSource = new(); private bool _isDefaultSetParameterSpecified = false; #region Parameters @@ -1677,7 +1676,7 @@ public SwitchParameter LoadUserProfile private SwitchParameter _loaduserprofile = SwitchParameter.Present; /// <summary> - /// Starts process in a new window. + /// Starts process in the current console window. /// </summary> [Parameter(ParameterSetName = "Default")] [Alias("nnw")] @@ -1904,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 @@ -1965,7 +1965,9 @@ protected override void BeginProcessing() startInfo.WindowStyle = _windowstyle; - if (_nonewwindow) + // When starting a process as another user, the 'CreateNoWindow' property value is ignored and a new window is created. + // See details at https://learn.microsoft.com/dotnet/api/system.diagnostics.processstartinfo.createnowindow?view=net-9.0#remarks + if (_nonewwindow && _credential is null) { startInfo.CreateNoWindow = _nonewwindow; } @@ -2062,7 +2064,7 @@ protected override void BeginProcessing() Process process = null; #if !UNIX - ProcessCollection jobObject = null; + using JobProcessCollection jobObject = new(); bool? jobAssigned = null; #endif if (startInfo.UseShellExecute) @@ -2100,7 +2102,6 @@ protected override void BeginProcessing() // https://github.com/PowerShell/PowerShell/issues/17033 if (Wait) { - jobObject = new(); jobAssigned = jobObject.AssignProcessToJobObject(processInfo.Process); } @@ -2151,23 +2152,20 @@ protected override void BeginProcessing() if (!process.HasExited) { #if UNIX - process.WaitForExit(); + process.WaitForExitAsync(_cancellationTokenSource.Token).GetAwaiter().GetResult(); #else - _waithandle = new ManualResetEvent(false); - - // Create and start the job object. This may have - // already been done in StartWithCreateProcess. - jobObject ??= new(); + // Add the process to the job, this may have already + // been done in StartWithCreateProcess. if (jobAssigned == true || (jobAssigned is null && jobObject.AssignProcessToJobObject(process.SafeHandle))) { // Wait for the job object to finish - jobObject.WaitOne(_waithandle); + jobObject.WaitForExit(_cancellationTokenSource.Token); } else { // WinBlue: 27537 Start-Process -Wait doesn't work in a remote session on Windows 7 or lower. // A Remote session is in it's own job and nested job support was only added in Windows 8/Server 2012. - process.WaitForExit(); + process.WaitForExitAsync(_cancellationTokenSource.Token).GetAwaiter().GetResult(); } #endif } @@ -2183,28 +2181,21 @@ protected override void BeginProcessing() /// <summary> /// Implements ^c, after creating a process. /// </summary> - protected override void StopProcessing() => _waithandle?.Set(); + protected override void StopProcessing() => _cancellationTokenSource.Cancel(); #endregion #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) - { - if (_waithandle != null) - { - _waithandle.Dispose(); - _waithandle = null; - } + _cancellationTokenSource.Dispose(); } #endregion @@ -2413,33 +2404,60 @@ private static byte[] ConvertEnvVarsToByteArray(StringDictionary sd) private void SetStartupInfo(ProcessStartInfo startinfo, ref ProcessNativeMethods.STARTUPINFO lpStartupInfo, ref int creationFlags) { - bool hasRedirection = false; + // If we are starting a process using the current console window, we need to set its standard handles + // explicitly when they are not redirected because otherwise they won't be set and the new process will + // fail with the "invalid handle" error. + // + // However, if we are starting a process with a new console window, we should not explicitly set those + // standard handles when they are not redirected, but instead let Windows figure out the default to use + // when creating the process. Otherwise, the standard input handles of the current window and the new + // window will get weirdly tied together and cause problems. + bool hasRedirection = startinfo.CreateNoWindow + || _redirectstandardinput is not null + || _redirectstandardoutput is not null + || _redirectstandarderror is not null; + // RedirectionStandardInput if (_redirectstandardinput != null) { - hasRedirection = true; startinfo.RedirectStandardInput = true; _redirectstandardinput = ResolveFilePath(_redirectstandardinput); lpStartupInfo.hStdInput = GetSafeFileHandleForRedirection(_redirectstandardinput, FileMode.Open); } + else if (startinfo.CreateNoWindow) + { + lpStartupInfo.hStdInput = new SafeFileHandle( + ProcessNativeMethods.GetStdHandle(-10), + ownsHandle: false); + } // RedirectionStandardOutput if (_redirectstandardoutput != null) { - hasRedirection = true; startinfo.RedirectStandardOutput = true; _redirectstandardoutput = ResolveFilePath(_redirectstandardoutput); lpStartupInfo.hStdOutput = GetSafeFileHandleForRedirection(_redirectstandardoutput, FileMode.Create); } + else if (startinfo.CreateNoWindow) + { + lpStartupInfo.hStdOutput = new SafeFileHandle( + ProcessNativeMethods.GetStdHandle(-11), + ownsHandle: false); + } // RedirectionStandardError if (_redirectstandarderror != null) { - hasRedirection = true; startinfo.RedirectStandardError = true; _redirectstandarderror = ResolveFilePath(_redirectstandarderror); lpStartupInfo.hStdError = GetSafeFileHandleForRedirection(_redirectstandarderror, FileMode.Create); } + else if (startinfo.CreateNoWindow) + { + lpStartupInfo.hStdError = new SafeFileHandle( + ProcessNativeMethods.GetStdHandle(-12), + ownsHandle: false); + } if (hasRedirection) { @@ -2672,7 +2690,7 @@ public IEnumerable<CompletionResult> CompleteArgument( // -Verb is not supported on non-Windows platforms as well as Windows headless SKUs if (!Platform.IsWindowsDesktop) { - yield break; + return Array.Empty<CompletionResult>(); } // Completion: Start-Process -FilePath <path> -Verb <wordToComplete> @@ -2684,12 +2702,7 @@ public IEnumerable<CompletionResult> CompleteArgument( // Complete file verbs if extension exists if (Path.HasExtension(filePath)) { - foreach (string verb in CompleteFileVerbs(filePath, wordToComplete)) - { - yield return new CompletionResult(verb); - } - - yield break; + return CompleteFileVerbs(wordToComplete, filePath); } // Otherwise check if command is an Application to resolve executable full path with extension @@ -2707,112 +2720,26 @@ public IEnumerable<CompletionResult> CompleteArgument( // Start-Process & Get-Command select first found application based on PATHEXT environment variable if (commands.Count >= 1) { - foreach (string verb in CompleteFileVerbs(commands[0].Source, wordToComplete)) - { - yield return new CompletionResult(verb); - } + return CompleteFileVerbs(wordToComplete, filePath: commands[0].Source); } } + + return Array.Empty<CompletionResult>(); } /// <summary> /// Completes file verbs. /// </summary> - /// <param name="filePath">The file path to get verbs.</param> /// <param name="wordToComplete">The word to complete.</param> + /// <param name="filePath">The file path to get verbs.</param> /// <returns>List of file verbs to complete.</returns> - private static IEnumerable<string> CompleteFileVerbs(string filePath, string wordToComplete) - { - var verbPattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase); - - string[] verbs = new ProcessStartInfo(filePath).Verbs; - - foreach (string verb in verbs) - { - if (verbPattern.IsMatch(verb)) - { - yield return verb; - } - } - } + private static IEnumerable<CompletionResult> CompleteFileVerbs(string wordToComplete, string filePath) + => CompletionHelpers.GetMatchingResults( + wordToComplete, + possibleCompletionValues: new ProcessStartInfo(filePath).Verbs); } #if !UNIX - /// <summary> - /// ProcessCollection is a helper class used by Start-Process -Wait cmdlet to monitor the - /// child processes created by the main process hosted by the Start-process cmdlet. - /// </summary> - internal class ProcessCollection - { - /// <summary> - /// JobObjectHandle is a reference to the job object used to track - /// the child processes created by the main process hosted by the Start-Process cmdlet. - /// </summary> - private readonly Microsoft.PowerShell.Commands.SafeJobHandle _jobObjectHandle; - - /// <summary> - /// ProcessCollection constructor. - /// </summary> - internal ProcessCollection() - { - IntPtr jobObjectHandleIntPtr = NativeMethods.CreateJobObject(IntPtr.Zero, null); - _jobObjectHandle = new SafeJobHandle(jobObjectHandleIntPtr); - } - - /// <summary> - /// Start API assigns the process to the JobObject and starts monitoring - /// the child processes hosted by the process created by Start-Process cmdlet. - /// </summary> - internal bool AssignProcessToJobObject(SafeProcessHandle process) - { - // Add the process to the job object - bool result = Interop.Windows.AssignProcessToJobObject( - _jobObjectHandle.DangerousGetHandle(), - process.DangerousGetHandle()); - return result; - } - - /// <summary> - /// Checks to see if the JobObject is empty (has no assigned processes). - /// If job is empty the auto reset event supplied as input would be set. - /// </summary> - internal void CheckJobStatus(object stateInfo) - { - ManualResetEvent emptyJobAutoEvent = (ManualResetEvent)stateInfo; - int dwSize = 0; - const int JOB_OBJECT_BASIC_PROCESS_ID_LIST = 3; - JOBOBJECT_BASIC_PROCESS_ID_LIST JobList = new(); - - dwSize = Marshal.SizeOf(JobList); - if (NativeMethods.QueryInformationJobObject(_jobObjectHandle, - JOB_OBJECT_BASIC_PROCESS_ID_LIST, - ref JobList, dwSize, IntPtr.Zero)) - { - if (JobList.NumberOfAssignedProcess == 0) - { - emptyJobAutoEvent.Set(); - } - } - } - - /// <summary> - /// WaitOne blocks the current thread until the current instance receives a signal, using - /// a System.TimeSpan to measure the time interval and specifying whether to - /// exit the synchronization domain before the wait. - /// </summary> - /// <param name="waitHandleToUse"> - /// WaitHandle to use for waiting on the job object. - /// </param> - internal void WaitOne(ManualResetEvent waitHandleToUse) - { - TimerCallback jobObjectStatusCb = this.CheckJobStatus; - using (Timer stateTimer = new(jobObjectStatusCb, waitHandleToUse, 0, 1000)) - { - waitHandleToUse.WaitOne(); - } - } - } - /// <summary> /// ProcessInformation is a helper class that wraps the native PROCESS_INFORMATION structure /// returned by CreateProcess or CreateProcessWithLogon. It ensures the process and thread @@ -2851,36 +2778,11 @@ public void Dispose() ~ProcessInformation() => Dispose(); } - /// <summary> - /// JOBOBJECT_BASIC_PROCESS_ID_LIST Contains the process identifier list for a job object. - /// If the job is nested, the process identifier list consists of all - /// processes associated with the job and its child jobs. - /// </summary> - [StructLayout(LayoutKind.Sequential)] - internal struct JOBOBJECT_BASIC_PROCESS_ID_LIST - { - /// <summary> - /// The number of process identifiers to be stored in ProcessIdList. - /// </summary> - public uint NumberOfAssignedProcess; - - /// <summary> - /// The number of process identifiers returned in the ProcessIdList buffer. - /// If this number is less than NumberOfAssignedProcesses, increase - /// the size of the buffer to accommodate the complete list. - /// </summary> - public uint NumberOfProcessIdsInList; - - /// <summary> - /// A variable-length array of process identifiers returned by this call. - /// Array elements 0 through NumberOfProcessIdsInList minus 1 - /// contain valid process identifiers. - /// </summary> - public IntPtr ProcessIdList; - } - internal static class ProcessNativeMethods { + [DllImport(PinvokeDllNames.GetStdHandleDllName, SetLastError = true)] + public static extern IntPtr GetStdHandle(int whichHandle); + [DllImport(PinvokeDllNames.CreateProcessWithLogonWDllName, CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool CreateProcessWithLogonW(string userName, @@ -2936,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; @@ -2974,7 +2876,7 @@ protected override bool ReleaseHandle() } [StructLayout(LayoutKind.Sequential)] - internal class STARTUPINFO + internal sealed class STARTUPINFO { public int cb; public IntPtr lpReserved; @@ -3037,21 +2939,6 @@ public void Dispose() } } } - - [SuppressUnmanagedCodeSecurity] - internal sealed class SafeJobHandle : SafeHandleZeroOrMinusOneIsInvalid - { - internal SafeJobHandle(IntPtr jobHandle) - : base(true) - { - base.SetHandle(jobHandle); - } - - protected override bool ReleaseHandle() - { - return Interop.Windows.CloseHandle(base.handle); - } - } #endif #endregion 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 b46f21dc68c..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) { - foreach (ServiceController dependantserv in service.DependentServices) + WriteObject(AddProperties(scManagerHandle, service)); + } + else + { + 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 @@ -2737,53 +2788,6 @@ bool SetServiceObjectSecurity( byte[] lpSecurityDescriptor ); - /// <summary> - /// CreateJobObject API creates or opens a job object. - /// </summary> - /// <param name="lpJobAttributes"> - /// A pointer to a SECURITY_ATTRIBUTES structure that specifies the security descriptor for the - /// job object and determines whether child processes can inherit the returned handle. - /// If lpJobAttributes is NULL, the job object gets a default security descriptor - /// and the handle cannot be inherited. - /// </param> - /// <param name="lpName"> - /// The name of the job. - /// </param> - /// <returns> - /// If the function succeeds, the return value is a handle to the job object. - /// If the object existed before the function call, the function - /// returns a handle to the existing job object. - /// </returns> - [DllImport("Kernel32.dll", CharSet = CharSet.Unicode)] - internal static extern IntPtr CreateJobObject(IntPtr lpJobAttributes, string lpName); - - /// <summary> - /// Retrieves job state information from the job object. - /// </summary> - /// <param name="hJob"> - /// A handle to the job whose information is being queried. - /// </param> - /// <param name="JobObjectInfoClass"> - /// The information class for the limits to be queried. - /// </param> - /// <param name="lpJobObjectInfo"> - /// The limit or job state information. - /// </param> - /// <param name="cbJobObjectLength"> - /// The count of the job information being queried, in bytes. - /// </param> - /// <param name="lpReturnLength"> - /// A pointer to a variable that receives the length of - /// data written to the structure pointed to by the lpJobObjectInfo parameter. - /// </param> - /// <returns>If the function succeeds, the return value is nonzero. - /// If the function fails, the return value is zero. - /// </returns> - [DllImport("Kernel32.dll", EntryPoint = "QueryInformationJobObject", SetLastError = true, CharSet = CharSet.Unicode)] - public static extern bool QueryInformationJobObject(SafeHandle hJob, int JobObjectInfoClass, - ref JOBOBJECT_BASIC_PROCESS_ID_LIST lpJobObjectInfo, - int cbJobObjectLength, IntPtr lpReturnLength); - internal static bool QueryServiceConfig(NakedWin32Handle hService, out NativeMethods.QUERY_SERVICE_CONFIG configStructure) { IntPtr lpBuffer = IntPtr.Zero; 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 0f012d39604..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.38.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.11.0" /> - <PackageReference Include="System.Threading.AccessControl" Version="9.0.0" /> - <PackageReference Include="System.Drawing.Common" Version="9.0.0" /> - <PackageReference Include="JsonSchema.Net" Version="7.2.3" /> + <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/AddMember.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddMember.cs index 604e54345d4..4c7601e883f 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddMember.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/AddMember.cs @@ -147,8 +147,8 @@ public SwitchParameter PassThru /// The name of the new NoteProperty member. /// </summary> [Parameter(Mandatory = true, Position = 0, ParameterSetName = NotePropertySingleMemberSet)] - [ValidateNotePropertyNameAttribute] - [NotePropertyTransformationAttribute] + [ValidateNotePropertyName] + [NotePropertyTransformation] [ValidateNotNullOrEmpty] public string NotePropertyName { 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 d4927a7876f..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); } } @@ -218,8 +208,8 @@ public string LiteralPath /// Gets or sets encoding optional flag. /// </summary> [Parameter] - [ArgumentToEncodingTransformationAttribute] - [ArgumentEncodingCompletionsAttribute] + [ArgumentToEncodingTransformation] + [ArgumentEncodingCompletions] [ValidateNotNullOrEmpty] public Encoding Encoding { @@ -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)); } @@ -602,8 +600,8 @@ public string[] LiteralPath /// Gets or sets encoding optional flag. /// </summary> [Parameter] - [ArgumentToEncodingTransformationAttribute] - [ArgumentEncodingCompletionsAttribute] + [ArgumentToEncodingTransformation] + [ArgumentEncodingCompletions] [ValidateNotNullOrEmpty] public Encoding Encoding { @@ -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-hex/Format-Hex.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-hex/Format-Hex.cs index 2f2536d361a..7e9dab8a203 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-hex/Format-Hex.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/format-hex/Format-Hex.cs @@ -68,8 +68,8 @@ public sealed class FormatHex : PSCmdlet /// Gets or sets the type of character encoding for InputObject. /// </summary> [Parameter(ParameterSetName = "ByInputObject")] - [ArgumentToEncodingTransformationAttribute] - [ArgumentEncodingCompletionsAttribute] + [ArgumentToEncodingTransformation] + [ArgumentEncodingCompletions] [ValidateNotNullOrEmpty] public Encoding Encoding { 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 cd965431c36..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,10 +42,16 @@ 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> - [ValidateRangeAttribute(1, int.MaxValue)] + [ValidateRange(1, int.MaxValue)] [Parameter] public int Depth { @@ -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 ba7cff6a08e..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 { @@ -59,7 +64,7 @@ public SwitchParameter AutoSize /// </summary> /// <value></value> [Parameter] - [ValidateRangeAttribute(1, int.MaxValue)] + [ValidateRange(1, int.MaxValue)] public int Column { get => _column.GetValueOrDefault(-1); @@ -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/FormatAndOutput/out-file/Out-File.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/out-file/Out-File.cs index 751b5ab0d99..e585fc1ce08 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/out-file/Out-File.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/out-file/Out-File.cs @@ -74,8 +74,8 @@ public string LiteralPath /// Encoding optional flag. /// </summary> [Parameter(Position = 1)] - [ArgumentToEncodingTransformationAttribute] - [ArgumentEncodingCompletionsAttribute] + [ArgumentToEncodingTransformation] + [ArgumentEncodingCompletions] [ValidateNotNullOrEmpty] public Encoding Encoding { @@ -136,7 +136,7 @@ public SwitchParameter NoClobber /// <summary> /// Optional, number of columns to use when writing to device. /// </summary> - [ValidateRangeAttribute(2, int.MaxValue)] + [ValidateRange(2, int.MaxValue)] [Parameter] public int Width { diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/out-string/Out-String.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/out-string/Out-String.cs index 512bf1048e0..0f485bec06a 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/out-string/Out-String.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/FormatAndOutput/out-string/Out-String.cs @@ -34,7 +34,7 @@ public SwitchParameter Stream /// <summary> /// Optional, number of columns to use when writing to device. /// </summary> - [ValidateRangeAttribute(2, int.MaxValue)] + [ValidateRange(2, int.MaxValue)] [Parameter] public int Width { 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/GetDateCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetDateCommand.cs index 62a2d276ebf..6717cc7196b 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetDateCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/GetDateCommand.cs @@ -78,7 +78,7 @@ public long UnixTimeSeconds /// Allows the user to override the year. /// </summary> [Parameter] - [ValidateRangeAttribute(1, 9999)] + [ValidateRange(1, 9999)] public int Year { get @@ -100,7 +100,7 @@ public int Year /// Allows the user to override the month. /// </summary> [Parameter] - [ValidateRangeAttribute(1, 12)] + [ValidateRange(1, 12)] public int Month { get @@ -122,7 +122,7 @@ public int Month /// Allows the user to override the day. /// </summary> [Parameter] - [ValidateRangeAttribute(1, 31)] + [ValidateRange(1, 31)] public int Day { get @@ -144,7 +144,7 @@ public int Day /// Allows the user to override the hour. /// </summary> [Parameter] - [ValidateRangeAttribute(0, 23)] + [ValidateRange(0, 23)] public int Hour { get @@ -166,7 +166,7 @@ public int Hour /// Allows the user to override the minute. /// </summary> [Parameter] - [ValidateRangeAttribute(0, 59)] + [ValidateRange(0, 59)] public int Minute { get @@ -188,7 +188,7 @@ public int Minute /// Allows the user to override the second. /// </summary> [Parameter] - [ValidateRangeAttribute(0, 59)] + [ValidateRange(0, 59)] public int Second { get @@ -210,7 +210,7 @@ public int Second /// Allows the user to override the millisecond. /// </summary> [Parameter] - [ValidateRangeAttribute(0, 999)] + [ValidateRange(0, 999)] public int Millisecond { get 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 3b7db715953..d01d144caca 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImplicitRemotingCommands.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/ImplicitRemotingCommands.cs @@ -73,8 +73,8 @@ public SwitchParameter Force /// Encoding optional flag. /// </summary> [Parameter] - [ArgumentToEncodingTransformationAttribute] - [ArgumentEncodingCompletionsAttribute] + [ArgumentToEncodingTransformation] + [ArgumentEncodingCompletions] [ValidateNotNullOrEmpty] public Encoding Encoding { @@ -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/MatchString.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MatchString.cs index 09bb2169c91..262fd44b30f 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MatchString.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/MatchString.cs @@ -1336,8 +1336,8 @@ public string[] Exclude /// Gets or sets the text encoding to process each file as. /// </summary> [Parameter] - [ArgumentToEncodingTransformationAttribute] - [ArgumentEncodingCompletionsAttribute] + [ArgumentToEncodingTransformation] + [ArgumentEncodingCompletions] [ValidateNotNullOrEmpty] public Encoding Encoding { 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 cf39a20a068..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() { @@ -179,7 +138,7 @@ public int First /// </summary> /// <value></value> [Parameter(ParameterSetName = "IndexParameter")] - [ValidateRangeAttribute(0, int.MaxValue)] + [ValidateRange(0, int.MaxValue)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public int[] Index { @@ -202,7 +161,7 @@ public int[] Index /// </summary> /// <value></value> [Parameter(ParameterSetName = "SkipIndexParameter")] - [ValidateRangeAttribute(0, int.MaxValue)] + [ValidateRange(0, int.MaxValue)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public int[] SkipIndex { @@ -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/Send-MailMessage.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Send-MailMessage.cs index 64ee818a6bd..2598d953496 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Send-MailMessage.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Send-MailMessage.cs @@ -61,8 +61,8 @@ public sealed class SendMailMessage : PSCmdlet [Parameter(ValueFromPipelineByPropertyName = true)] [Alias("BE")] [ValidateNotNullOrEmpty] - [ArgumentEncodingCompletionsAttribute] - [ArgumentToEncodingTransformationAttribute] + [ArgumentEncodingCompletions] + [ArgumentToEncodingTransformation] public Encoding Encoding { 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 36849814375..839a0b7c051 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/StartSleepCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/StartSleepCommand.cs @@ -44,14 +44,14 @@ public void Dispose() /// </summary> [Parameter(Position = 0, Mandatory = true, ParameterSetName = "Seconds", ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)] - [ValidateRangeAttribute(0.0, (double)(int.MaxValue / 1000))] + [ValidateRange(0.0, (double)(int.MaxValue / 1000))] public double Seconds { get; set; } /// <summary> /// Allows sleep time to be specified in milliseconds. /// </summary> [Parameter(Mandatory = true, ParameterSetName = "Milliseconds", ValueFromPipelineByPropertyName = true)] - [ValidateRangeAttribute(0, int.MaxValue)] + [ValidateRange(0, int.MaxValue)] [Alias("ms")] public int Milliseconds { get; set; } @@ -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 d8b3261c118..4a0f4831299 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Tee-Object.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Tee-Object.cs @@ -78,8 +78,8 @@ public SwitchParameter Append /// </summary> [Parameter(ParameterSetName = "File")] [Parameter(ParameterSetName = "LiteralFile")] - [ArgumentToEncodingTransformationAttribute] - [ArgumentEncodingCompletionsAttribute] + [ArgumentToEncodingTransformation] + [ArgumentEncodingCompletions] [ValidateNotNullOrEmpty] public Encoding Encoding { get; set; } = Encoding.Default; @@ -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/Update-TypeData.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Update-TypeData.cs index bb70f3097ab..b73d8570040 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Update-TypeData.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/Update-TypeData.cs @@ -260,7 +260,7 @@ public string[] PropertySerializationSet /// The type name we want to update on. /// </summary> [Parameter(Mandatory = true, ValueFromPipeline = true, ParameterSetName = DynamicTypeSet)] - [ArgumentToTypeNameTransformationAttribute] + [ArgumentToTypeNameTransformation] [ValidateNotNullOrEmpty] public string TypeName { @@ -1051,7 +1051,7 @@ public class RemoveTypeDataCommand : PSCmdlet /// The target type to remove. /// </summary> [Parameter(Mandatory = true, Position = 0, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true, ParameterSetName = RemoveTypeSet)] - [ArgumentToTypeNameTransformationAttribute] + [ArgumentToTypeNameTransformation] [ValidateNotNullOrEmpty] public string TypeName { 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/WaitEventCommand.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WaitEventCommand.cs index a8ca247671c..3c4336f07d0 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WaitEventCommand.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WaitEventCommand.cs @@ -42,7 +42,7 @@ public string SourceIdentifier /// </summary> [Parameter] [Alias("TimeoutSec")] - [ValidateRangeAttribute(-1, int.MaxValue)] + [ValidateRange(-1, int.MaxValue)] public int Timeout { get 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/WriteConsoleCmdlet.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteConsoleCmdlet.cs index b9c4e2d454a..48d84636ce2 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteConsoleCmdlet.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WriteConsoleCmdlet.cs @@ -4,6 +4,7 @@ using System.Collections; using System.Management.Automation; using System.Text; +using System.Xml; namespace Microsoft.PowerShell.Commands { @@ -59,6 +60,10 @@ private string ProcessObject(object o) return s; } } + else if (o is XmlNode xmlNode) + { + return xmlNode.Name; + } else if (o is IEnumerable enumerable) { // unroll enumerables, including arrays. diff --git a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/XmlCommands.cs b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/XmlCommands.cs index 1e27eae31b8..cc3b1f2b251 100644 --- a/src/Microsoft.PowerShell.Commands.Utility/commands/utility/XmlCommands.cs +++ b/src/Microsoft.PowerShell.Commands.Utility/commands/utility/XmlCommands.cs @@ -111,8 +111,8 @@ public SwitchParameter NoClobber /// Encoding optional flag. /// </summary> [Parameter] - [ArgumentToEncodingTransformationAttribute()] - [ArgumentEncodingCompletionsAttribute] + [ArgumentToEncodingTransformation] + [ArgumentEncodingCompletions] [ValidateNotNullOrEmpty] public Encoding Encoding { @@ -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 8cc7ee00f57..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); @@ -622,15 +667,18 @@ public override PSHostUserInterface UI /// <summary> /// See base class. /// </summary> - public void PushRunspace(Runspace newRunspace) + public void PushRunspace(Runspace runspace) { if (_runspaceRef == null) { return; } - RemoteRunspace remoteRunspace = newRunspace as RemoteRunspace; - Dbg.Assert(remoteRunspace != null, "Expected remoteRunspace != null"); + if (runspace is not RemoteRunspace remoteRunspace) + { + throw new ArgumentException(ConsoleHostStrings.PushRunspaceNotRemote, nameof(runspace)); + } + remoteRunspace.StateChanged += HandleRemoteRunspaceStateChanged; // Unsubscribe the local session debugger. @@ -1630,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: @@ -1709,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 @@ -1720,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; } } @@ -1876,6 +1886,7 @@ private void DoRunspaceInitialization(RunspaceCreationEventArgs args) { s_theConsoleHost.UI.WriteLine(ManagedEntranceStrings.ShellBannerCLAuditMode); } + break; case PSLanguageMode.NoLanguage: @@ -2190,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); } } @@ -2211,7 +2221,7 @@ private void ReportExceptionFallback(Exception e, string header) Console.Error.WriteLine(header); } - if (e == null) + if (e is null) { return; } @@ -2219,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) { @@ -2236,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> @@ -2557,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 @@ -2742,6 +2761,7 @@ e is RemoteException || #endif } } + // NTRAID#Windows Out Of Band Releases-915506-2005/09/09 // Removed HandleUnexpectedExceptions infrastructure finally @@ -2887,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 _); @@ -3050,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 6e3359275e0..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 @@ -1331,13 +1342,6 @@ public override void WriteProgress(long sourceId, ProgressRecord record) { Dbg.Assert(record != null, "WriteProgress called with null ProgressRecord"); - if (Console.IsOutputRedirected) - { - // Do not write progress bar when the stdout is redirected. - return; - } - - // We allow only one thread at a time to update the progress state.) if (_parent.ErrorFormat == Serialization.DataFormat.XML) { PSObject obj = new PSObject(); @@ -1345,8 +1349,14 @@ public override void WriteProgress(long sourceId, ProgressRecord record) obj.Properties.Add(new PSNoteProperty("Record", record)); _parent.ErrorSerializer.Serialize(obj, "progress"); } + else if (Console.IsOutputRedirected) + { + // Do not write progress bar when the stdout is redirected. + return; + } else { + // We allow only one thread at a time to update the progress state.) lock (_instanceLock) { HandleIncomingProgressRecord(sourceId, record); @@ -1386,6 +1396,7 @@ public override void WriteErrorLine(string value) } else { + value = GetOutputString(value, SupportsVirtualTerminal); Console.Error.WriteLine(value); } } @@ -2251,7 +2262,7 @@ internal void HandleThrowOnReadAndPrompt() private readonly ConsoleHostRawUserInterface _rawui; private readonly ConsoleHost _parent; - [TraceSourceAttribute("ConsoleHostUserInterface", "Console host's subclass of S.M.A.Host.Console")] + [TraceSource("ConsoleHostUserInterface", "Console host's subclass of S.M.A.Host.Console")] private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("ConsoleHostUserInterface", "Console host's subclass of S.M.A.Host.Console"); } } // namespace 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/StartTranscriptCmdlet.cs b/src/Microsoft.PowerShell.ConsoleHost/host/msh/StartTranscriptCmdlet.cs index b220eae3bf8..18725b5ddb7 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/host/msh/StartTranscriptCmdlet.cs +++ b/src/Microsoft.PowerShell.ConsoleHost/host/msh/StartTranscriptCmdlet.cs @@ -177,7 +177,7 @@ protected override void BeginProcessing() } else { - _outFilename = (string)value; + _outFilename = (string)PSObject.Base(value); } } 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 ce124ec084c..9bc06e0d42f 100644 --- a/src/Microsoft.PowerShell.ConsoleHost/resources/ConsoleHostStrings.resx +++ b/src/Microsoft.PowerShell.ConsoleHost/resources/ConsoleHostStrings.resx @@ -182,4 +182,10 @@ The current session does not support debugging; execution will continue. <data name="RunAsAdministrator" xml:space="preserve"> <value>Run as Administrator</value> </data> + <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 40f02e7a37f..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 | -? | /? @@ -478,7 +475,7 @@ All parameters are case-insensitive.</value> -WindowStyle | -w Sets the window style for the session. Valid values are Normal, Minimized, - Maximized and Hidden. + Maximized, and Hidden. -WorkingDirectory | -wd 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 5c28e4fe256..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="9.0.0" /> + <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 15b503e3d1a..50d431225c7 100644 --- a/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj +++ b/src/Microsoft.PowerShell.SDK/Microsoft.PowerShell.SDK.csproj @@ -16,27 +16,40 @@ <ItemGroup> <!-- This section is to force the version of non-direct dependencies --> - <PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" /> - <PackageReference Include="Microsoft.Extensions.ObjectPool" Version="8.0.11" /> + <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.8.6" /> + <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="9.0.0" /> - <PackageReference Include="System.Net.Http.WinHttpHandler" Version="9.0.0" /> - <PackageReference Include="System.Text.Encodings.Web" Version="9.0.0" /> - <!-- - 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" /> - <PackageReference Include="System.ServiceModel.Http" Version="4.10.3" /> - <PackageReference Include="System.ServiceModel.NetTcp" Version="4.10.3" /> - <PackageReference Include="System.ServiceModel.Primitives" Version="4.10.3" /> - <PackageReference Include="System.ServiceModel.Security" Version="4.10.3" /> - <PackageReference Include="System.Private.ServiceModel" Version="4.10.3" /> + <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="9.0.0" /> + <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 527bf6be675..37c687a7770 100644 --- a/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs +++ b/src/Microsoft.PowerShell.Security/security/CertificateProvider.cs @@ -3309,20 +3309,30 @@ public EnhancedKeyUsageProperty(X509Certificate2 cert) public sealed class DnsNameProperty { private readonly List<DnsNameRepresentation> _dnsList = new(); - private readonly System.Globalization.IdnMapping idnMapping = new(); + private readonly IdnMapping idnMapping = new(); - private const string dnsNamePrefix = "DNS Name="; private const string distinguishedNamePrefix = "CN="; /// <summary> /// Get property of DnsNameList. /// </summary> - public List<DnsNameRepresentation> DnsNameList + public List<DnsNameRepresentation> DnsNameList => _dnsList; + + private DnsNameRepresentation GetDnsNameRepresentation(string dnsName) { - get + string unicodeName; + + try + { + unicodeName = idnMapping.GetUnicode(dnsName); + } + catch (ArgumentException) { - return _dnsList; + // The name is not valid Punycode, assume it's valid ASCII. + unicodeName = dnsName; } + + return new DnsNameRepresentation(dnsName, unicodeName); } /// <summary> @@ -3330,61 +3340,32 @@ public List<DnsNameRepresentation> DnsNameList /// </summary> public DnsNameProperty(X509Certificate2 cert) { - string name; - string unicodeName; - DnsNameRepresentation dnsName; _dnsList = new List<DnsNameRepresentation>(); // extract DNS name from subject distinguish name // if it exists and does not contain a comma // a comma, indicates it is not a DNS name - if (cert.Subject.StartsWith(distinguishedNamePrefix, System.StringComparison.OrdinalIgnoreCase) && + if (cert.Subject.StartsWith(distinguishedNamePrefix, StringComparison.OrdinalIgnoreCase) && !cert.Subject.Contains(',')) { - name = cert.Subject.Substring(distinguishedNamePrefix.Length); - try - { - unicodeName = idnMapping.GetUnicode(name); - } - catch (System.ArgumentException) - { - // The name is not valid punyCode, assume it's valid ascii. - unicodeName = name; - } - - dnsName = new DnsNameRepresentation(name, unicodeName); + string parsedSubjectDistinguishedDnsName = cert.Subject.Substring(distinguishedNamePrefix.Length); + DnsNameRepresentation dnsName = GetDnsNameRepresentation(parsedSubjectDistinguishedDnsName); _dnsList.Add(dnsName); } + // Extract DNS names from SAN extensions foreach (X509Extension extension in cert.Extensions) { - // Filter to the OID for Subject Alternative Name - if (extension.Oid.Value == "2.5.29.17") + if (extension is X509SubjectAlternativeNameExtension sanExtension) { - string[] names = extension.Format(true).Split(Environment.NewLine); - foreach (string nameLine in names) + foreach (string dnsNameEntry in sanExtension.EnumerateDnsNames()) { - // Get the part after 'DNS Name=' - if (nameLine.StartsWith(dnsNamePrefix, System.StringComparison.InvariantCultureIgnoreCase)) - { - name = nameLine.Substring(dnsNamePrefix.Length); - try - { - unicodeName = idnMapping.GetUnicode(name); - } - catch (System.ArgumentException) - { - // The name is not valid punyCode, assume it's valid ascii. - unicodeName = name; - } - - dnsName = new DnsNameRepresentation(name, unicodeName); + DnsNameRepresentation dnsName = GetDnsNameRepresentation(dnsNameEntry); - // Only add the name if it is not the same as an existing name. - if (!_dnsList.Contains(dnsName)) - { - _dnsList.Add(dnsName); - } + // Only add the name if it is not the same as an existing name. + if (!_dnsList.Contains(dnsName)) + { + _dnsList.Add(dnsName); } } } @@ -3443,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; @@ -3478,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 6685770b0ef..3abb938a572 100644 --- a/src/Microsoft.WSMan.Management/Interop.cs +++ b/src/Microsoft.WSMan.Management/Interop.cs @@ -173,7 +173,7 @@ public enum AuthenticationMechanism [ComImport] [TypeLibType((short)4304)] #if CORECLR - [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] #else [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)] #endif @@ -255,7 +255,7 @@ string Error [ComImport] [TypeLibType((short)4288)] #if CORECLR - [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] #else [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)] #endif @@ -312,7 +312,7 @@ string Password [ComImport] [TypeLibType((short)4288)] #if CORECLR - [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] #else [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)] #endif @@ -336,7 +336,7 @@ string CertificateThumbprint [ComImport] [TypeLibType((short)4288)] #if CORECLR - [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] #else [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)] #endif @@ -386,7 +386,7 @@ void SetProxy(int accessType, [ComImport] [TypeLibType((short)4288)] #if CORECLR - [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] #else [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)] #endif @@ -450,7 +450,7 @@ string Error [TypeLibType((short)4304)] [SuppressMessage("Microsoft.Naming", "CA1711:IdentifiersShouldNotHaveIncorrectSuffix")] #if CORECLR - [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] #else [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)] #endif @@ -706,7 +706,7 @@ string Error [ComImport] [TypeLibType((short)4288)] #if CORECLR - [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] #else [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)] #endif @@ -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> @@ -860,7 +863,7 @@ string Error [ComImport] [TypeLibType((short)4288)] #if CORECLR - [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] #else [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)] #endif @@ -1005,7 +1008,7 @@ int Timeout [ComImport] [TypeLibType((short)400)] #if CORECLR - [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] #else [InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch)] #endif diff --git a/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj b/src/Microsoft.WSMan.Management/Microsoft.WSMan.Management.csproj index 89a147cb855..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="9.0.0" /> + <PackageReference Include="System.ServiceProcess.ServiceController" Version="11.0.0-preview.3.26207.106" /> </ItemGroup> <PropertyGroup> diff --git a/src/Microsoft.WSMan.Management/WSManInstance.cs b/src/Microsoft.WSMan.Management/WSManInstance.cs index a29741172e2..c96b002123d 100644 --- a/src/Microsoft.WSMan.Management/WSManInstance.cs +++ b/src/Microsoft.WSMan.Management/WSManInstance.cs @@ -326,7 +326,7 @@ public Uri ResourceURI [Parameter(ParameterSetName = "Enumerate")] [ValidateNotNullOrEmpty] - [ValidateSetAttribute(new string[] { "object", "epr", "objectandepr" })] + [ValidateSet(new string[] { "object", "epr", "objectandepr" })] [Alias("RT")] public string ReturnType { 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 9df1121f38b..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>net9.0</TargetFramework> + <TargetFramework>net11.0</TargetFramework> <SuppressNETCoreSdkPreviewMessage>true</SuppressNETCoreSdkPreviewMessage> </PropertyGroup> @@ -13,10 +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.0-rc2" /> + <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.0.3" /> + <PackageReference Include="PSReadLine" Version="2.4.5" /> + <PackageReference Include="Microsoft.PowerShell.ThreadJob" Version="2.2.0" /> </ItemGroup> </Project> diff --git a/src/Modules/Shared/Microsoft.PowerShell.Host/Microsoft.PowerShell.Host.psd1 b/src/Modules/Shared/Microsoft.PowerShell.Host/Microsoft.PowerShell.Host.psd1 index e6d616aa61d..3c2581795f7 100644 --- a/src/Modules/Shared/Microsoft.PowerShell.Host/Microsoft.PowerShell.Host.psd1 +++ b/src/Modules/Shared/Microsoft.PowerShell.Host/Microsoft.PowerShell.Host.psd1 @@ -10,5 +10,5 @@ FunctionsToExport = @() CmdletsToExport="Start-Transcript", "Stop-Transcript" AliasesToExport = @() NestedModules="Microsoft.PowerShell.ConsoleHost.dll" -HelpInfoURI = 'https://aka.ms/powershell73-help' +HelpInfoURI = 'https://aka.ms/powershell75-help' } diff --git a/src/Modules/Unix/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 b/src/Modules/Unix/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 index 6eb576cdf03..21563c1da7c 100644 --- a/src/Modules/Unix/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 +++ b/src/Modules/Unix/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 @@ -7,7 +7,7 @@ ModuleVersion="7.0.0.0" CompatiblePSEditions = @("Core") PowerShellVersion="3.0" NestedModules="Microsoft.PowerShell.Commands.Management.dll" -HelpInfoURI = 'https://aka.ms/powershell73-help' +HelpInfoURI = 'https://aka.ms/powershell75-help' FunctionsToExport = @() AliasesToExport = @("gcb", "gtz", "scb") CmdletsToExport=@("Add-Content", diff --git a/src/Modules/Unix/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 b/src/Modules/Unix/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 index 8268326aa74..adab0df2849 100644 --- a/src/Modules/Unix/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 +++ b/src/Modules/Unix/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 @@ -10,5 +10,5 @@ FunctionsToExport = @() CmdletsToExport = "Get-Credential", "Get-ExecutionPolicy", "Set-ExecutionPolicy", "ConvertFrom-SecureString", "ConvertTo-SecureString", "Get-PfxCertificate" , "Protect-CmsMessage", "Unprotect-CmsMessage", "Get-CmsMessage" AliasesToExport = @() NestedModules = "Microsoft.PowerShell.Security.dll" -HelpInfoURI = 'https://aka.ms/powershell73-help' +HelpInfoURI = 'https://aka.ms/powershell75-help' } diff --git a/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 b/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 index 1d31d5889e8..df841837696 100644 --- a/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 +++ b/src/Modules/Unix/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 @@ -31,5 +31,5 @@ CmdletsToExport = @( FunctionsToExport = @() AliasesToExport = @('fhx') NestedModules = @("Microsoft.PowerShell.Commands.Utility.dll") -HelpInfoURI = 'https://aka.ms/powershell73-help' +HelpInfoURI = 'https://aka.ms/powershell75-help' } diff --git a/src/Modules/Windows/CimCmdlets/CimCmdlets.psd1 b/src/Modules/Windows/CimCmdlets/CimCmdlets.psd1 index 93c68321ca1..734fe45016d 100644 --- a/src/Modules/Windows/CimCmdlets/CimCmdlets.psd1 +++ b/src/Modules/Windows/CimCmdlets/CimCmdlets.psd1 @@ -14,5 +14,5 @@ CmdletsToExport= "Get-CimAssociatedInstance", "Get-CimClass", "Get-CimInstance", "Remove-CimSession","Set-CimInstance", "Export-BinaryMiLog","Import-BinaryMiLog" AliasesToExport = "gcim","scim","ncim", "rcim","icim","gcai","rcie","ncms","rcms","gcms","ncso","gcls" -HelpInfoUri="https://aka.ms/powershell73-help" +HelpInfoUri="https://aka.ms/powershell75-help" } diff --git a/src/Modules/Windows/Microsoft.PowerShell.Diagnostics/Microsoft.PowerShell.Diagnostics.psd1 b/src/Modules/Windows/Microsoft.PowerShell.Diagnostics/Microsoft.PowerShell.Diagnostics.psd1 index 50282d8d5b8..7f77777b137 100644 --- a/src/Modules/Windows/Microsoft.PowerShell.Diagnostics/Microsoft.PowerShell.Diagnostics.psd1 +++ b/src/Modules/Windows/Microsoft.PowerShell.Diagnostics/Microsoft.PowerShell.Diagnostics.psd1 @@ -12,5 +12,5 @@ AliasesToExport = @() NestedModules="Microsoft.PowerShell.Commands.Diagnostics.dll" TypesToProcess="GetEvent.types.ps1xml" FormatsToProcess="Event.format.ps1xml", "Diagnostics.format.ps1xml" -HelpInfoURI = 'https://aka.ms/powershell73-help' +HelpInfoURI = 'https://aka.ms/powershell75-help' } diff --git a/src/Modules/Windows/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 b/src/Modules/Windows/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 index 0b49f178b25..f7582920935 100644 --- a/src/Modules/Windows/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 +++ b/src/Modules/Windows/Microsoft.PowerShell.Management/Microsoft.PowerShell.Management.psd1 @@ -7,7 +7,7 @@ ModuleVersion="7.0.0.0" CompatiblePSEditions = @("Core") PowerShellVersion="3.0" NestedModules="Microsoft.PowerShell.Commands.Management.dll" -HelpInfoURI = 'https://aka.ms/powershell73-help' +HelpInfoURI = 'https://aka.ms/powershell75-help' FunctionsToExport = @() AliasesToExport = @("gcb", "gin", "gtz", "scb", "stz") CmdletsToExport=@("Add-Content", diff --git a/src/Modules/Windows/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 b/src/Modules/Windows/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 index 7470c795fdc..0953b2d1cca 100644 --- a/src/Modules/Windows/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 +++ b/src/Modules/Windows/Microsoft.PowerShell.Security/Microsoft.PowerShell.Security.psd1 @@ -14,5 +14,5 @@ NestedModules = "Microsoft.PowerShell.Security.dll" # We declare 'Microsoft.PowerShell.Security.dll' in 'RequiredAssemblies' so as to make sure it's loaded before the type file processing. RequiredAssemblies = "Microsoft.PowerShell.Security.dll" TypesToProcess = "Security.types.ps1xml" -HelpInfoURI = 'https://aka.ms/powershell73-help' +HelpInfoURI = 'https://aka.ms/powershell75-help' } diff --git a/src/Modules/Windows/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 b/src/Modules/Windows/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 index 33db09feb9c..2043543a8a5 100644 --- a/src/Modules/Windows/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 +++ b/src/Modules/Windows/Microsoft.PowerShell.Utility/Microsoft.PowerShell.Utility.psd1 @@ -29,5 +29,5 @@ CmdletsToExport = @( FunctionsToExport = @() AliasesToExport = @('fhx') NestedModules = @("Microsoft.PowerShell.Commands.Utility.dll") -HelpInfoURI = 'https://aka.ms/powershell73-help' +HelpInfoURI = 'https://aka.ms/powershell75-help' } diff --git a/src/Modules/Windows/Microsoft.WSMan.Management/Microsoft.WSMan.Management.psd1 b/src/Modules/Windows/Microsoft.WSMan.Management/Microsoft.WSMan.Management.psd1 index 5eb367b7e7f..ced706c9fde 100644 --- a/src/Modules/Windows/Microsoft.WSMan.Management/Microsoft.WSMan.Management.psd1 +++ b/src/Modules/Windows/Microsoft.WSMan.Management/Microsoft.WSMan.Management.psd1 @@ -11,5 +11,5 @@ CmdletsToExport="Disable-WSManCredSSP", "Enable-WSManCredSSP", "Get-WSManCredSSP AliasesToExport = @() NestedModules="Microsoft.WSMan.Management.dll" FormatsToProcess="WSMan.format.ps1xml" -HelpInfoURI = 'https://aka.ms/powershell73-help' +HelpInfoURI = 'https://aka.ms/powershell75-help' } diff --git a/src/Modules/Windows/PSDiagnostics/PSDiagnostics.psd1 b/src/Modules/Windows/PSDiagnostics/PSDiagnostics.psd1 index 6185b589a82..3b53d6740e5 100644 --- a/src/Modules/Windows/PSDiagnostics/PSDiagnostics.psd1 +++ b/src/Modules/Windows/PSDiagnostics/PSDiagnostics.psd1 @@ -10,5 +10,5 @@ FunctionsToExport="Disable-PSTrace","Disable-PSWSManCombinedTrace","Disable-WSManTrace","Enable-PSTrace","Enable-PSWSManCombinedTrace","Enable-WSManTrace","Get-LogProperties","Set-LogProperties","Start-Trace","Stop-Trace" CmdletsToExport = @() AliasesToExport = @() - HelpInfoUri="https://aka.ms/powershell73-help" + HelpInfoUri="https://aka.ms/powershell75-help" } 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 a6448b1dc7d..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>net9.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/CoreCLR/CorePsStub.cs b/src/System.Management.Automation/CoreCLR/CorePsStub.cs index 6f94acbc389..e439cc30ff2 100644 --- a/src/System.Management.Automation/CoreCLR/CorePsStub.cs +++ b/src/System.Management.Automation/CoreCLR/CorePsStub.cs @@ -431,7 +431,7 @@ namespace System.Management.Automation.Security /// <summary> /// Application white listing security policies only affect Windows OSs. /// </summary> - internal sealed class SystemPolicy + public sealed class SystemPolicy { private SystemPolicy() { } @@ -455,7 +455,7 @@ internal static void LogWDACAuditMessage( /// <summary> /// Gets the system lockdown policy. /// </summary> - /// <remarks>Always return SystemEnforcementMode.None in CSS (trusted)</remarks> + /// <remarks>Always return SystemEnforcementMode.None on non-Windows platforms.</remarks> public static SystemEnforcementMode GetSystemLockdownPolicy() { return SystemEnforcementMode.None; @@ -464,7 +464,7 @@ public static SystemEnforcementMode GetSystemLockdownPolicy() /// <summary> /// Gets lockdown policy as applied to a file. /// </summary> - /// <remarks>Always return SystemEnforcementMode.None in CSS (trusted)</remarks> + /// <remarks>Always return SystemEnforcementMode.None on non-Windows platforms.</remarks> public static SystemEnforcementMode GetLockdownPolicy(string path, System.Runtime.InteropServices.SafeHandle handle) { return SystemEnforcementMode.None; @@ -493,7 +493,7 @@ public static SystemScriptFileEnforcement GetFilePolicyEnforcement( /// <summary> /// How the policy is being enforced. /// </summary> - internal enum SystemEnforcementMode + public enum SystemEnforcementMode { /// Not enforced at all None = 0, 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/FormatAndOutput/out-console/OutConsole.cs b/src/System.Management.Automation/FormatAndOutput/out-console/OutConsole.cs index 60c5c2c0007..20fcf54e593 100644 --- a/src/System.Management.Automation/FormatAndOutput/out-console/OutConsole.cs +++ b/src/System.Management.Automation/FormatAndOutput/out-console/OutConsole.cs @@ -14,7 +14,7 @@ namespace Microsoft.PowerShell.Commands /// <summary> /// Null sink to absorb pipeline output. /// </summary> - [CmdletAttribute("Out", "Null", SupportsShouldProcess = false, + [Cmdlet("Out", "Null", SupportsShouldProcess = false, HelpUri = "https://go.microsoft.com/fwlink/?LinkID=2096792", RemotingCapability = RemotingCapability.None)] public class OutNullCommand : PSCmdlet { diff --git a/src/System.Management.Automation/SourceGenerators/PSVersionInfoGenerator/PSVersionInfoGenerator.csproj b/src/System.Management.Automation/SourceGenerators/PSVersionInfoGenerator/PSVersionInfoGenerator.csproj index 5e7287debb6..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>11.0</LangVersion> + <LangVersion>preview</LangVersion> <SuppressNETCoreSdkPreviewMessage>true</SuppressNETCoreSdkPreviewMessage> <EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules> <Nullable>enable</Nullable> </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" PrivateAssets="all" /> - <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" PrivateAssets="all" /> + <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="5.3.0" PrivateAssets="all" /> + <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="5.3.0" PrivateAssets="all" /> </ItemGroup> </Project> diff --git a/src/System.Management.Automation/System.Management.Automation.csproj b/src/System.Management.Automation/System.Management.Automation.csproj index c00aed6617b..f3e1d0dd9e8 100644 --- a/src/System.Management.Automation/System.Management.Automation.csproj +++ b/src/System.Management.Automation/System.Management.Automation.csproj @@ -28,25 +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.22.0" /> + <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="9.0.0" /> - <PackageReference Include="System.Configuration.ConfigurationManager" Version="9.0.0" /> - <PackageReference Include="System.Diagnostics.DiagnosticSource" Version="9.0.0" /> - <PackageReference Include="System.DirectoryServices" Version="9.0.0" /> - <!--PackageReference Include="System.IO.FileSystem.AccessControl" Version="6.0.0-preview.5.21301.5" /--> - <PackageReference Include="System.Management" Version="9.0.0" /> - <PackageReference Include="System.Security.AccessControl" Version="6.0.1" /> - <PackageReference Include="System.Security.Cryptography.Pkcs" Version="9.0.0" /> - <PackageReference Include="System.Security.Permissions" Version="9.0.0" /> - <PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.0" /> + <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.3.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/CoreCLR/cmdlets-over-objects.objectModel.autogen.cs b/src/System.Management.Automation/cimSupport/cmdletization/xml/CoreCLR/cmdlets-over-objects.objectModel.autogen.cs index 8614c6a3994..76b7a3c2c52 100644 --- a/src/System.Management.Automation/cimSupport/cmdletization/xml/CoreCLR/cmdlets-over-objects.objectModel.autogen.cs +++ b/src/System.Management.Automation/cimSupport/cmdletization/xml/CoreCLR/cmdlets-over-objects.objectModel.autogen.cs @@ -23,8 +23,8 @@ namespace Microsoft.PowerShell.Cmdletization.Xml /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] - [System.Xml.Serialization.XmlRootAttribute(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11", IsNullable = false)] + [System.Xml.Serialization.XmlType(AnonymousType = true, Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlRoot(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11", IsNullable = false)] internal partial class PowerShellMetadata { private ClassMetadata _classField; @@ -46,7 +46,7 @@ public ClassMetadata Class } /// <remarks/> - [System.Xml.Serialization.XmlArrayItemAttribute("Enum", IsNullable = false)] + [System.Xml.Serialization.XmlArrayItem("Enum", IsNullable = false)] public EnumMetadataEnum[] Enums { get @@ -64,7 +64,7 @@ public EnumMetadataEnum[] Enums /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class ClassMetadata { private string _versionField; @@ -126,7 +126,7 @@ public ClassMetadataInstanceCmdlets InstanceCmdlets } /// <remarks/> - [System.Xml.Serialization.XmlArrayItemAttribute("Cmdlet", IsNullable = false)] + [System.Xml.Serialization.XmlArrayItem("Cmdlet", IsNullable = false)] public StaticCmdletMetadata[] StaticCmdlets { get @@ -141,7 +141,7 @@ public StaticCmdletMetadata[] StaticCmdlets } /// <remarks/> - [System.Xml.Serialization.XmlArrayItemAttribute("Data", IsNullable = false)] + [System.Xml.Serialization.XmlArrayItem("Data", IsNullable = false)] public ClassMetadataData[] CmdletAdapterPrivateData { get @@ -156,7 +156,7 @@ public ClassMetadataData[] CmdletAdapterPrivateData } /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string CmdletAdapter { get @@ -171,7 +171,7 @@ public string CmdletAdapter } /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string ClassName { get @@ -186,7 +186,7 @@ public string ClassName } /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string ClassVersion { get @@ -204,7 +204,7 @@ public string ClassVersion /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(AnonymousType = true, Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class ClassMetadataInstanceCmdlets { private GetCmdletParameters _getCmdletParametersField; @@ -242,7 +242,7 @@ public GetCmdletMetadata GetCmdlet } /// <remarks/> - [System.Xml.Serialization.XmlElementAttribute("Cmdlet")] + [System.Xml.Serialization.XmlElement("Cmdlet")] public InstanceCmdletMetadata[] Cmdlet { get @@ -260,7 +260,7 @@ public InstanceCmdletMetadata[] Cmdlet /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class GetCmdletParameters { private PropertyMetadata[] _queryablePropertiesField; @@ -272,7 +272,7 @@ internal partial class GetCmdletParameters private string _defaultCmdletParameterSetField; /// <remarks/> - [System.Xml.Serialization.XmlArrayItemAttribute("Property", IsNullable = false)] + [System.Xml.Serialization.XmlArrayItem("Property", IsNullable = false)] public PropertyMetadata[] QueryableProperties { get @@ -287,7 +287,7 @@ public PropertyMetadata[] QueryableProperties } /// <remarks/> - [System.Xml.Serialization.XmlArrayItemAttribute(IsNullable = false)] + [System.Xml.Serialization.XmlArrayItem(IsNullable = false)] public Association[] QueryableAssociations { get @@ -302,7 +302,7 @@ public Association[] QueryableAssociations } /// <remarks/> - [System.Xml.Serialization.XmlArrayItemAttribute("Option", IsNullable = false)] + [System.Xml.Serialization.XmlArrayItem("Option", IsNullable = false)] public QueryOption[] QueryOptions { get @@ -317,7 +317,7 @@ public QueryOption[] QueryOptions } /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string DefaultCmdletParameterSet { get @@ -335,7 +335,7 @@ public string DefaultCmdletParameterSet /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class PropertyMetadata { private TypeMetadata _typeField; @@ -361,11 +361,11 @@ public TypeMetadata Type } /// <remarks/> - [System.Xml.Serialization.XmlElementAttribute("ExcludeQuery", typeof(WildcardablePropertyQuery))] - [System.Xml.Serialization.XmlElementAttribute("MaxValueQuery", typeof(PropertyQuery))] - [System.Xml.Serialization.XmlElementAttribute("MinValueQuery", typeof(PropertyQuery))] - [System.Xml.Serialization.XmlElementAttribute("RegularQuery", typeof(WildcardablePropertyQuery))] - [System.Xml.Serialization.XmlChoiceIdentifierAttribute("ItemsElementName")] + [System.Xml.Serialization.XmlElement("ExcludeQuery", typeof(WildcardablePropertyQuery))] + [System.Xml.Serialization.XmlElement("MaxValueQuery", typeof(PropertyQuery))] + [System.Xml.Serialization.XmlElement("MinValueQuery", typeof(PropertyQuery))] + [System.Xml.Serialization.XmlElement("RegularQuery", typeof(WildcardablePropertyQuery))] + [System.Xml.Serialization.XmlChoiceIdentifier("ItemsElementName")] public PropertyQuery[] Items { get @@ -380,8 +380,8 @@ public PropertyQuery[] Items } /// <remarks/> - [System.Xml.Serialization.XmlElementAttribute("ItemsElementName")] - [System.Xml.Serialization.XmlIgnoreAttribute()] + [System.Xml.Serialization.XmlElement("ItemsElementName")] + [System.Xml.Serialization.XmlIgnore()] public ItemsChoiceType[] ItemsElementName { get @@ -396,7 +396,7 @@ public ItemsChoiceType[] ItemsElementName } /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string PropertyName { get @@ -414,7 +414,7 @@ public string PropertyName /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class TypeMetadata { private string _pSTypeField; @@ -422,7 +422,7 @@ internal partial class TypeMetadata private string _eTSTypeField; /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string PSType { get @@ -437,7 +437,7 @@ public string PSType } /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string ETSType { get @@ -455,7 +455,7 @@ public string ETSType /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class Association { private AssociationAssociatedInstance _associatedInstanceField; @@ -481,7 +481,7 @@ public AssociationAssociatedInstance AssociatedInstance } /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute("Association")] + [System.Xml.Serialization.XmlAttribute("Association")] public string Association1 { get @@ -496,7 +496,7 @@ public string Association1 } /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string SourceRole { get @@ -511,7 +511,7 @@ public string SourceRole } /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string ResultRole { get @@ -529,7 +529,7 @@ public string ResultRole /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(AnonymousType = true, Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class AssociationAssociatedInstance { private TypeMetadata _typeField; @@ -568,7 +568,7 @@ public CmdletParameterMetadataForGetCmdletFilteringParameter CmdletParameterMeta /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class CmdletParameterMetadataForGetCmdletFilteringParameter : CmdletParameterMetadataForGetCmdletParameter { private bool _errorOnNoMatchField; @@ -576,7 +576,7 @@ internal partial class CmdletParameterMetadataForGetCmdletFilteringParameter : C private bool _errorOnNoMatchFieldSpecified; /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public bool ErrorOnNoMatch { get @@ -591,7 +591,7 @@ public bool ErrorOnNoMatch } /// <remarks/> - [System.Xml.Serialization.XmlIgnoreAttribute()] + [System.Xml.Serialization.XmlIgnore()] public bool ErrorOnNoMatchSpecified { get @@ -607,10 +607,10 @@ public bool ErrorOnNoMatchSpecified } /// <remarks/> - [System.Xml.Serialization.XmlIncludeAttribute(typeof(CmdletParameterMetadataForGetCmdletFilteringParameter))] + [System.Xml.Serialization.XmlInclude(typeof(CmdletParameterMetadataForGetCmdletFilteringParameter))] [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class CmdletParameterMetadataForGetCmdletParameter : CmdletParameterMetadata { private bool _valueFromPipelineField; @@ -624,7 +624,7 @@ internal partial class CmdletParameterMetadataForGetCmdletParameter : CmdletPara private string[] _cmdletParameterSetsField; /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public bool ValueFromPipeline { get @@ -639,7 +639,7 @@ public bool ValueFromPipeline } /// <remarks/> - [System.Xml.Serialization.XmlIgnoreAttribute()] + [System.Xml.Serialization.XmlIgnore()] public bool ValueFromPipelineSpecified { get @@ -654,7 +654,7 @@ public bool ValueFromPipelineSpecified } /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public bool ValueFromPipelineByPropertyName { get @@ -669,7 +669,7 @@ public bool ValueFromPipelineByPropertyName } /// <remarks/> - [System.Xml.Serialization.XmlIgnoreAttribute()] + [System.Xml.Serialization.XmlIgnore()] public bool ValueFromPipelineByPropertyNameSpecified { get @@ -684,7 +684,7 @@ public bool ValueFromPipelineByPropertyNameSpecified } /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string[] CmdletParameterSets { get @@ -700,13 +700,13 @@ public string[] CmdletParameterSets } /// <remarks/> - [System.Xml.Serialization.XmlIncludeAttribute(typeof(CmdletParameterMetadataForGetCmdletParameter))] - [System.Xml.Serialization.XmlIncludeAttribute(typeof(CmdletParameterMetadataForGetCmdletFilteringParameter))] - [System.Xml.Serialization.XmlIncludeAttribute(typeof(CmdletParameterMetadataForInstanceMethodParameter))] - [System.Xml.Serialization.XmlIncludeAttribute(typeof(CmdletParameterMetadataForStaticMethodParameter))] + [System.Xml.Serialization.XmlInclude(typeof(CmdletParameterMetadataForGetCmdletParameter))] + [System.Xml.Serialization.XmlInclude(typeof(CmdletParameterMetadataForGetCmdletFilteringParameter))] + [System.Xml.Serialization.XmlInclude(typeof(CmdletParameterMetadataForInstanceMethodParameter))] + [System.Xml.Serialization.XmlInclude(typeof(CmdletParameterMetadataForStaticMethodParameter))] [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class CmdletParameterMetadata { private object _allowEmptyCollectionField; @@ -852,7 +852,7 @@ public CmdletParameterMetadataValidateRange ValidateRange } /// <remarks/> - [System.Xml.Serialization.XmlArrayItemAttribute("AllowedValue", IsNullable = false)] + [System.Xml.Serialization.XmlArrayItem("AllowedValue", IsNullable = false)] public string[] ValidateSet { get @@ -881,7 +881,7 @@ public ObsoleteAttributeMetadata Obsolete } /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public bool IsMandatory { get @@ -896,7 +896,7 @@ public bool IsMandatory } /// <remarks/> - [System.Xml.Serialization.XmlIgnoreAttribute()] + [System.Xml.Serialization.XmlIgnore()] public bool IsMandatorySpecified { get @@ -911,7 +911,7 @@ public bool IsMandatorySpecified } /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string[] Aliases { get @@ -926,7 +926,7 @@ public string[] Aliases } /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string PSName { get @@ -941,7 +941,7 @@ public string PSName } /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute(DataType = "nonNegativeInteger")] + [System.Xml.Serialization.XmlAttribute(DataType = "nonNegativeInteger")] public string Position { get @@ -959,7 +959,7 @@ public string Position /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(AnonymousType = true, Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class CmdletParameterMetadataValidateCount { private string _minField; @@ -967,7 +967,7 @@ internal partial class CmdletParameterMetadataValidateCount private string _maxField; /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute(DataType = "nonNegativeInteger")] + [System.Xml.Serialization.XmlAttribute(DataType = "nonNegativeInteger")] public string Min { get @@ -982,7 +982,7 @@ public string Min } /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute(DataType = "nonNegativeInteger")] + [System.Xml.Serialization.XmlAttribute(DataType = "nonNegativeInteger")] public string Max { get @@ -1000,7 +1000,7 @@ public string Max /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(AnonymousType = true, Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class CmdletParameterMetadataValidateLength { private string _minField; @@ -1008,7 +1008,7 @@ internal partial class CmdletParameterMetadataValidateLength private string _maxField; /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute(DataType = "nonNegativeInteger")] + [System.Xml.Serialization.XmlAttribute(DataType = "nonNegativeInteger")] public string Min { get @@ -1023,7 +1023,7 @@ public string Min } /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute(DataType = "nonNegativeInteger")] + [System.Xml.Serialization.XmlAttribute(DataType = "nonNegativeInteger")] public string Max { get @@ -1041,7 +1041,7 @@ public string Max /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(AnonymousType = true, Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class CmdletParameterMetadataValidateRange { private string _minField; @@ -1049,7 +1049,7 @@ internal partial class CmdletParameterMetadataValidateRange private string _maxField; /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute(DataType = "integer")] + [System.Xml.Serialization.XmlAttribute(DataType = "integer")] public string Min { get @@ -1064,7 +1064,7 @@ public string Min } /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute(DataType = "integer")] + [System.Xml.Serialization.XmlAttribute(DataType = "integer")] public string Max { get @@ -1082,13 +1082,13 @@ public string Max /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class ObsoleteAttributeMetadata { private string _messageField; /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string Message { get @@ -1106,7 +1106,7 @@ public string Message /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class CmdletParameterMetadataForInstanceMethodParameter : CmdletParameterMetadata { private bool _valueFromPipelineByPropertyNameField; @@ -1114,7 +1114,7 @@ internal partial class CmdletParameterMetadataForInstanceMethodParameter : Cmdle private bool _valueFromPipelineByPropertyNameFieldSpecified; /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public bool ValueFromPipelineByPropertyName { get @@ -1129,7 +1129,7 @@ public bool ValueFromPipelineByPropertyName } /// <remarks/> - [System.Xml.Serialization.XmlIgnoreAttribute()] + [System.Xml.Serialization.XmlIgnore()] public bool ValueFromPipelineByPropertyNameSpecified { get @@ -1147,7 +1147,7 @@ public bool ValueFromPipelineByPropertyNameSpecified /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class CmdletParameterMetadataForStaticMethodParameter : CmdletParameterMetadata { private bool _valueFromPipelineField; @@ -1159,7 +1159,7 @@ internal partial class CmdletParameterMetadataForStaticMethodParameter : CmdletP private bool _valueFromPipelineByPropertyNameFieldSpecified; /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public bool ValueFromPipeline { get @@ -1174,7 +1174,7 @@ public bool ValueFromPipeline } /// <remarks/> - [System.Xml.Serialization.XmlIgnoreAttribute()] + [System.Xml.Serialization.XmlIgnore()] public bool ValueFromPipelineSpecified { get @@ -1189,7 +1189,7 @@ public bool ValueFromPipelineSpecified } /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public bool ValueFromPipelineByPropertyName { get @@ -1204,7 +1204,7 @@ public bool ValueFromPipelineByPropertyName } /// <remarks/> - [System.Xml.Serialization.XmlIgnoreAttribute()] + [System.Xml.Serialization.XmlIgnore()] public bool ValueFromPipelineByPropertyNameSpecified { get @@ -1222,7 +1222,7 @@ public bool ValueFromPipelineByPropertyNameSpecified /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class QueryOption { private TypeMetadata _typeField; @@ -1260,7 +1260,7 @@ public CmdletParameterMetadataForGetCmdletParameter CmdletParameterMetadata } /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string OptionName { get @@ -1278,7 +1278,7 @@ public string OptionName /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class GetCmdletMetadata { private CommonCmdletMetadata _cmdletMetadataField; @@ -1317,7 +1317,7 @@ public GetCmdletParameters GetCmdletParameters /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class CommonCmdletMetadata { private ObsoleteAttributeMetadata _obsoleteField; @@ -1349,7 +1349,7 @@ public ObsoleteAttributeMetadata Obsolete } /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string Verb { get @@ -1364,7 +1364,7 @@ public string Verb } /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string Noun { get @@ -1379,7 +1379,7 @@ public string Noun } /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string[] Aliases { get @@ -1394,7 +1394,7 @@ public string[] Aliases } /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public ConfirmImpact ConfirmImpact { get @@ -1409,7 +1409,7 @@ public ConfirmImpact ConfirmImpact } /// <remarks/> - [System.Xml.Serialization.XmlIgnoreAttribute()] + [System.Xml.Serialization.XmlIgnore()] public bool ConfirmImpactSpecified { get @@ -1424,7 +1424,7 @@ public bool ConfirmImpactSpecified } /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute(DataType = "anyURI")] + [System.Xml.Serialization.XmlAttribute(DataType = "anyURI")] public string HelpUri { get @@ -1441,7 +1441,7 @@ public string HelpUri /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] public enum ConfirmImpact { /// <remarks/> @@ -1460,7 +1460,7 @@ public enum ConfirmImpact /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class StaticCmdletMetadata { private StaticCmdletMetadataCmdletMetadata _cmdletMetadataField; @@ -1482,7 +1482,7 @@ public StaticCmdletMetadataCmdletMetadata CmdletMetadata } /// <remarks/> - [System.Xml.Serialization.XmlElementAttribute("Method")] + [System.Xml.Serialization.XmlElement("Method")] public StaticMethodMetadata[] Method { get @@ -1500,13 +1500,13 @@ public StaticMethodMetadata[] Method /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(AnonymousType = true, Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class StaticCmdletMetadataCmdletMetadata : CommonCmdletMetadata { private string _defaultCmdletParameterSetField; /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string DefaultCmdletParameterSet { get @@ -1524,7 +1524,7 @@ public string DefaultCmdletParameterSet /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class StaticMethodMetadata : CommonMethodMetadata { private StaticMethodParameterMetadata[] _parametersField; @@ -1532,7 +1532,7 @@ internal partial class StaticMethodMetadata : CommonMethodMetadata private string _cmdletParameterSetField; /// <remarks/> - [System.Xml.Serialization.XmlArrayItemAttribute("Parameter", IsNullable = false)] + [System.Xml.Serialization.XmlArrayItem("Parameter", IsNullable = false)] public StaticMethodParameterMetadata[] Parameters { get @@ -1547,7 +1547,7 @@ public StaticMethodParameterMetadata[] Parameters } /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string CmdletParameterSet { get @@ -1565,7 +1565,7 @@ public string CmdletParameterSet /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class StaticMethodParameterMetadata : CommonMethodParameterMetadata { private CmdletParameterMetadataForStaticMethodParameter _cmdletParameterMetadataField; @@ -1604,7 +1604,7 @@ public CmdletOutputMetadata CmdletOutputMetadata /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class CmdletOutputMetadata { private object _errorCodeField; @@ -1626,7 +1626,7 @@ public object ErrorCode } /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string PSName { get @@ -1642,11 +1642,11 @@ public string PSName } /// <remarks/> - [System.Xml.Serialization.XmlIncludeAttribute(typeof(InstanceMethodParameterMetadata))] - [System.Xml.Serialization.XmlIncludeAttribute(typeof(StaticMethodParameterMetadata))] + [System.Xml.Serialization.XmlInclude(typeof(InstanceMethodParameterMetadata))] + [System.Xml.Serialization.XmlInclude(typeof(StaticMethodParameterMetadata))] [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class CommonMethodParameterMetadata { private TypeMetadata _typeField; @@ -1670,7 +1670,7 @@ public TypeMetadata Type } /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string ParameterName { get @@ -1685,7 +1685,7 @@ public string ParameterName } /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string DefaultValue { get @@ -1703,7 +1703,7 @@ public string DefaultValue /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class InstanceMethodParameterMetadata : CommonMethodParameterMetadata { private CmdletParameterMetadataForInstanceMethodParameter _cmdletParameterMetadataField; @@ -1740,11 +1740,11 @@ public CmdletOutputMetadata CmdletOutputMetadata } /// <remarks/> - [System.Xml.Serialization.XmlIncludeAttribute(typeof(InstanceMethodMetadata))] - [System.Xml.Serialization.XmlIncludeAttribute(typeof(StaticMethodMetadata))] + [System.Xml.Serialization.XmlInclude(typeof(InstanceMethodMetadata))] + [System.Xml.Serialization.XmlInclude(typeof(StaticMethodMetadata))] [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class CommonMethodMetadata { private CommonMethodMetadataReturnValue _returnValueField; @@ -1766,7 +1766,7 @@ public CommonMethodMetadataReturnValue ReturnValue } /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string MethodName { get @@ -1784,7 +1784,7 @@ public string MethodName /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(AnonymousType = true, Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class CommonMethodMetadataReturnValue { private TypeMetadata _typeField; @@ -1823,13 +1823,13 @@ public CmdletOutputMetadata CmdletOutputMetadata /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class InstanceMethodMetadata : CommonMethodMetadata { private InstanceMethodParameterMetadata[] _parametersField; /// <remarks/> - [System.Xml.Serialization.XmlArrayItemAttribute("Parameter", IsNullable = false)] + [System.Xml.Serialization.XmlArrayItem("Parameter", IsNullable = false)] public InstanceMethodParameterMetadata[] Parameters { get @@ -1847,7 +1847,7 @@ public InstanceMethodParameterMetadata[] Parameters /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class InstanceCmdletMetadata { private CommonCmdletMetadata _cmdletMetadataField; @@ -1900,10 +1900,10 @@ public GetCmdletParameters GetCmdletParameters } /// <remarks/> - [System.Xml.Serialization.XmlIncludeAttribute(typeof(WildcardablePropertyQuery))] + [System.Xml.Serialization.XmlInclude(typeof(WildcardablePropertyQuery))] [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class PropertyQuery { private CmdletParameterMetadataForGetCmdletFilteringParameter _cmdletParameterMetadataField; @@ -1926,7 +1926,7 @@ public CmdletParameterMetadataForGetCmdletFilteringParameter CmdletParameterMeta /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class WildcardablePropertyQuery : PropertyQuery { private bool _allowGlobbingField; @@ -1934,7 +1934,7 @@ internal partial class WildcardablePropertyQuery : PropertyQuery private bool _allowGlobbingFieldSpecified; /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public bool AllowGlobbing { get @@ -1949,7 +1949,7 @@ public bool AllowGlobbing } /// <remarks/> - [System.Xml.Serialization.XmlIgnoreAttribute()] + [System.Xml.Serialization.XmlIgnore()] public bool AllowGlobbingSpecified { get @@ -1966,7 +1966,7 @@ public bool AllowGlobbingSpecified /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] - [System.Xml.Serialization.XmlTypeAttribute(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11", IncludeInSchema = false)] + [System.Xml.Serialization.XmlType(Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11", IncludeInSchema = false)] public enum ItemsChoiceType { /// <remarks/> @@ -1985,7 +1985,7 @@ public enum ItemsChoiceType /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(AnonymousType = true, Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class ClassMetadataData { private string _nameField; @@ -1993,7 +1993,7 @@ internal partial class ClassMetadataData private string _valueField; /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string Name { get @@ -2008,7 +2008,7 @@ public string Name } /// <remarks/> - [System.Xml.Serialization.XmlTextAttribute()] + [System.Xml.Serialization.XmlText()] public string Value { get @@ -2026,7 +2026,7 @@ public string Value /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(AnonymousType = true, Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class EnumMetadataEnum { private EnumMetadataEnumValue[] _valueField; @@ -2040,7 +2040,7 @@ internal partial class EnumMetadataEnum private bool _bitwiseFlagsFieldSpecified; /// <remarks/> - [System.Xml.Serialization.XmlElementAttribute("Value")] + [System.Xml.Serialization.XmlElement("Value")] public EnumMetadataEnumValue[] Value { get @@ -2055,7 +2055,7 @@ public EnumMetadataEnumValue[] Value } /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string EnumName { get @@ -2070,7 +2070,7 @@ public string EnumName } /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string UnderlyingType { get @@ -2085,7 +2085,7 @@ public string UnderlyingType } /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public bool BitwiseFlags { get @@ -2100,7 +2100,7 @@ public bool BitwiseFlags } /// <remarks/> - [System.Xml.Serialization.XmlIgnoreAttribute()] + [System.Xml.Serialization.XmlIgnore()] public bool BitwiseFlagsSpecified { get @@ -2118,7 +2118,7 @@ public bool BitwiseFlagsSpecified /// <remarks/> [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.0.30319.17929")] [System.Diagnostics.DebuggerStepThroughAttribute()] - [System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] + [System.Xml.Serialization.XmlType(AnonymousType = true, Namespace = "http://schemas.microsoft.com/cmdlets-over-objects/2009/11")] internal partial class EnumMetadataEnumValue { private string _nameField; @@ -2126,7 +2126,7 @@ internal partial class EnumMetadataEnumValue private string _valueField; /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute()] + [System.Xml.Serialization.XmlAttribute()] public string Name { get @@ -2141,7 +2141,7 @@ public string Name } /// <remarks/> - [System.Xml.Serialization.XmlAttributeAttribute(DataType = "integer")] + [System.Xml.Serialization.XmlAttribute(DataType = "integer")] public string Value { get 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 9e698c05174..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", @@ -1877,7 +1877,7 @@ protected override void Validate(object arguments, EngineIntrinsics engineIntrin /// <summary> /// Allows a NULL as the argument to a mandatory parameter. /// </summary> - [AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Property)] + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public sealed class AllowNullAttribute : CmdletMetadataAttribute { /// <summary> @@ -1889,7 +1889,7 @@ public AllowNullAttribute() { } /// <summary> /// Allows an empty string as the argument to a mandatory string parameter. /// </summary> - [AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Property)] + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public sealed class AllowEmptyStringAttribute : CmdletMetadataAttribute { /// <summary> @@ -1901,7 +1901,7 @@ public AllowEmptyStringAttribute() { } /// <summary> /// Allows an empty collection as the argument to a mandatory collection parameter. /// </summary> - [AttributeUsageAttribute(AttributeTargets.Field | AttributeTargets.Property)] + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] public sealed class AllowEmptyCollectionAttribute : CmdletMetadataAttribute { /// <summary> @@ -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/CommandBase.cs b/src/System.Management.Automation/engine/CommandBase.cs index 4d90a7c2490..3708611df07 100644 --- a/src/System.Management.Automation/engine/CommandBase.cs +++ b/src/System.Management.Automation/engine/CommandBase.cs @@ -8,8 +8,7 @@ using System.Management.Automation.Internal.Host; using System.Management.Automation.Language; using System.Management.Automation.Runspaces; - -using Dbg = System.Management.Automation.Diagnostics; +using System.Threading; namespace System.Management.Automation.Internal { @@ -132,6 +131,13 @@ internal bool IsStopping } } + /// <summary> + /// Gets the CancellationToken that is signaled when the pipeline is stopping. + /// </summary> + internal CancellationToken StopToken => commandRuntime is MshCommandRuntime mcr + ? mcr.PipelineProcessor.PipelineStopToken + : default; + /// <summary> /// The information about the command. /// </summary> diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionAnalysis.cs index e98f577090c..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); } @@ -693,6 +917,19 @@ internal List<CompletionResult> GetResultHelper(CompletionContext completionCont return completions; } } + else if (lastAst is VariableExpressionAst && lastAst.Parent is ParameterAst paramAst && paramAst.Attributes.Count > 0) + { + foreach (AttributeBaseAst attribute in paramAst.Attributes) + { + if (IsCursorWithinOrJustAfterExtent(_cursorPosition, attribute.Extent)) + { + completionContext.ReplacementIndex = replacementIndex += tokenAtCursor.Text.Length; + completionContext.ReplacementLength = replacementLength = 0; + result = GetResultForAttributeArgument(completionContext, ref replacementIndex, ref replacementLength); + break; + } + } + } else { // Handle scenarios such as 'configuration foo { File ab { Attributes =' @@ -928,6 +1165,18 @@ internal List<CompletionResult> GetResultHelper(CompletionContext completionCont { result = GetResultForAttributeArgument(completionContext, ref replacementIndex, ref replacementLength); } + + if (lastAst is VariableExpressionAst && lastAst.Parent is ParameterAst paramAst && paramAst.Attributes.Count > 0) + { + foreach (AttributeBaseAst attribute in paramAst.Attributes) + { + if (IsCursorWithinOrJustAfterExtent(_cursorPosition, attribute.Extent)) + { + result = GetResultForAttributeArgument(completionContext, ref replacementIndex, ref replacementLength); + break; + } + } + } break; case TokenKind.Ieq: @@ -1003,6 +1252,21 @@ internal List<CompletionResult> GetResultHelper(CompletionContext completionCont break; } + if (lastAst is VariableExpressionAst && lastAst.Parent is ParameterAst paramAst && paramAst.Attributes.Count > 0) + { + foreach (AttributeBaseAst attribute in paramAst.Attributes) + { + if (IsCursorWithinOrJustAfterExtent(_cursorPosition, attribute.Extent)) + { + completionContext.ReplacementLength = replacementLength = 0; + result = GetResultForAttributeArgument(completionContext, ref replacementIndex, ref replacementLength); + break; + } + } + + break; + } + result = GetResultForEnumPropertyValueOfDSCResource(completionContext, string.Empty, ref replacementIndex, ref replacementLength, out _); break; } @@ -1421,18 +1685,115 @@ private static List<CompletionResult> CompletePropertyAssignment(MemberExpressio { if (SafeExprEvaluator.TrySafeEval(memberExpression, context.ExecutionContext, out var evalValue)) { - if (evalValue is null) + if (evalValue is not null) { + Type type = evalValue.GetType(); + if (type.IsEnum) + { + return GetResultForEnum(type, context); + } + return null; } + } - Type type = evalValue.GetType(); - if (type.IsEnum) + _ = TryGetInferredCompletionsForAssignment(memberExpression, context, out List<CompletionResult> result); + return result; + } + + private static bool TryGetInferredCompletionsForAssignment(Ast expression, CompletionContext context, out List<CompletionResult> result) + { + result = null; + IList<PSTypeName> inferredTypes; + if (expression.Parent is ConvertExpressionAst convertExpression) + { + inferredTypes = new PSTypeName[] { new(convertExpression.Type.TypeName) }; + } + else if (expression is MemberExpressionAst) + { + inferredTypes = AstTypeInference.InferTypeOf(expression); + } + else if (expression is VariableExpressionAst varExpression) + { + PSTypeName typeConstraint = CompletionCompleters.GetLastDeclaredTypeConstraint(varExpression, context.TypeInferenceContext); + if (typeConstraint is null) { - return GetResultForEnum(type, context); + return false; } + + inferredTypes = new PSTypeName[] { typeConstraint }; } - return null; + else + { + return false; + } + + if (inferredTypes.Count == 0) + { + return false; + } + + var values = new SortedSet<string>(); + foreach (PSTypeName type in inferredTypes) + { + Type loadedType = type.Type; + if (loadedType is not null) + { + if (loadedType.IsEnum) + { + foreach (string value in Enum.GetNames(loadedType)) + { + _ = values.Add(value); + } + } + } + else if (type is not null && type.TypeDefinitionAst.IsEnum) + { + foreach (MemberAst member in type.TypeDefinitionAst.Members) + { + if (member is PropertyMemberAst property) + { + _ = values.Add(property.Name); + } + } + } + } + + string wordToComplete; + if (string.IsNullOrEmpty(context.WordToComplete)) + { + if (context.TokenAtCursor is not null && context.TokenAtCursor.Kind != TokenKind.Equals) + { + wordToComplete = context.TokenAtCursor.Text + "*"; + } + else + { + wordToComplete = "*"; + } + } + else + { + wordToComplete = context.WordToComplete + "*"; + } + + result = new List<CompletionResult>(); + var pattern = new WildcardPattern(wordToComplete, WildcardOptions.IgnoreCase); + foreach (string name in values) + { + string quotedName = GetQuotedString(name, context); + if (pattern.IsMatch(quotedName)) + { + result.Add(new CompletionResult(quotedName, name, CompletionResultType.Property, name)); + } + } + + if (result.Count == 0) + { + result = null; + return false; + } + + return true; } private static bool TryGetCompletionsForVariableAssignment( @@ -1501,7 +1862,7 @@ bool TryGetResultForSet(Type typeConstraint, ValidateSetAttribute setConstraint, // If the assignment itself was unconstrained, the variable still might be if (!TryGetTypeConstraintOnVariable(completionContext, variableAst.VariablePath.UserPath, out typeConstraint, out setConstraint)) { - return false; + return TryGetInferredCompletionsForAssignment(variableAst, completionContext, out completions); } // Again try the [ValidateSet()] constraint first @@ -1778,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)) @@ -1939,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); @@ -1963,7 +2353,12 @@ private static List<CompletionResult> GetResultForIdentifier(CompletionContext c switch (usingState.UsingStatementKind) { case UsingStatementKind.Assembly: - break; + HashSet<string> assemblyExtensions = new(StringComparer.OrdinalIgnoreCase) + { + StringLiterals.PowerShellILAssemblyExtension + }; + return CompletionCompleters.CompleteFilename(completionContext, containerOnly: false, assemblyExtensions).ToList(); + case UsingStatementKind.Command: break; case UsingStatementKind.Module: @@ -1998,9 +2393,25 @@ private static List<CompletionResult> GetResultForIdentifier(CompletionContext c } } } - if (completionContext.TokenAtCursor.TokenFlags == TokenFlags.MemberName && (lastAst is NamedAttributeArgumentAst || lastAst.Parent is NamedAttributeArgumentAst)) + + if (completionContext.TokenAtCursor.TokenFlags == TokenFlags.MemberName) { - result = GetResultForAttributeArgument(completionContext, ref replacementIndex, ref replacementLength); + if (lastAst is NamedAttributeArgumentAst || lastAst.Parent is NamedAttributeArgumentAst) + { + result = GetResultForAttributeArgument(completionContext, ref replacementIndex, ref replacementLength); + } + else if (lastAst is VariableExpressionAst && lastAst.Parent is ParameterAst paramAst && paramAst.Attributes.Count > 0) + { + foreach (AttributeBaseAst attribute in paramAst.Attributes) + { + if (IsCursorWithinOrJustAfterExtent(completionContext.CursorPosition, attribute.Extent)) + { + result = GetResultForAttributeArgument(completionContext, ref replacementIndex, ref replacementLength); + break; + } + } + } + if (result is not null) { return result; @@ -2455,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 2694bb7e950..80cead788d3 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionCompleters.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; @@ -63,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; @@ -88,7 +88,7 @@ private static List<CompletionResult> CompleteCommand(CompletionContext context, var addAmpersandIfNecessary = IsAmpersandNeeded(context, false); string commandName = context.WordToComplete; - string quote = HandleDoubleAndSingleQuote(ref commandName); + string quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref commandName); List<CompletionResult> commandResults = null; @@ -233,7 +233,7 @@ internal static CompletionResult GetCommandNameCompletionResult(string name, obj syntax = string.IsNullOrEmpty(syntax) ? name : syntax; bool needAmpersand; - if (CompletionRequiresQuotes(name, false)) + if (CompletionHelpers.CompletionRequiresQuotes(name)) { needAmpersand = quote == string.Empty && addAmpersandIfNecessary; string quoteInUse = quote == string.Empty ? "'" : quote; @@ -328,50 +328,72 @@ internal static List<CompletionResult> MakeCommandsUnique(IEnumerable<PSObject> } } - List<CompletionResult> endResults = null; foreach (var keyValuePair in commandTable) { - var commandList = keyValuePair.Value as List<object>; - if (commandList != null) + if (keyValuePair.Value is List<object> commandList) { - endResults ??= new List<CompletionResult>(); - - // The first command might be an un-prefixed commandInfo that we get by importing a module with the -Prefix parameter, - // in that case, we should add the module name qualification because if the module is not in the module path, calling - // 'Get-Foo' directly doesn't work - string completionName = keyValuePair.Key; - if (!includeModulePrefix) + var modulesWithCommand = new HashSet<string>(StringComparer.OrdinalIgnoreCase); + var importedModules = new HashSet<string>(StringComparer.OrdinalIgnoreCase); + var commandInfoList = new List<CommandInfo>(commandList.Count); + for (int i = 0; i < commandList.Count; i++) { - var commandInfo = commandList[0] as CommandInfo; - if (commandInfo != null && !string.IsNullOrEmpty(commandInfo.Prefix)) + if (commandList[i] is not CommandInfo commandInfo) { - Diagnostics.Assert(!string.IsNullOrEmpty(commandInfo.ModuleName), "the module name should exist if commandInfo.Prefix is not an empty string"); - if (!ModuleCmdletBase.IsPrefixedCommand(commandInfo)) - { - completionName = commandInfo.ModuleName + "\\" + completionName; - } + continue; + } + + commandInfoList.Add(commandInfo); + if (commandInfo.CommandType == CommandTypes.Application) + { + continue; + } + + modulesWithCommand.Add(commandInfo.ModuleName); + if ((commandInfo.CommandType == CommandTypes.Cmdlet && commandInfo.CommandMetadata.CommandType is not null) + || (commandInfo.CommandType is CommandTypes.Function or CommandTypes.Filter && commandInfo.Definition != string.Empty) + || (commandInfo.CommandType == CommandTypes.Alias && commandInfo.Definition is not null)) + { + // Checks if the command or source module has been imported. + _ = importedModules.Add(commandInfo.ModuleName); } } - results.Add(GetCommandNameCompletionResult(completionName, commandList[0], addAmpersandIfNecessary, quote)); + if (commandInfoList.Count == 0) + { + continue; + } - // For the other commands that are hidden, we need to disambiguate, - // but put these at the end as it's less likely any of the hidden - // commands are desired. If we can't add anything to disambiguate, - // then we'll skip adding a completion result. - for (int index = 1; index < commandList.Count; index++) + int moduleCount = modulesWithCommand.Count; + modulesWithCommand.Clear(); + int index; + if (commandInfoList[0].CommandType == CommandTypes.Application + || importedModules.Count == 1 + || moduleCount < 2) + { + // We can use the short name for this command because there's no ambiguity about which command it resolves to. + // If the first element is an application then we know there's no conflicting commands/aliases (because of the command precedence). + // If there's just 1 module imported then the short name refers to that module (and it will be the first element in the list) + // If there's less than 2 unique modules exporting that command then we can use the short name because it can only refer to that module. + index = 1; + results.Add(GetCommandNameCompletionResult(keyValuePair.Key, commandInfoList[0], addAmpersandIfNecessary, quote)); + modulesWithCommand.Add(commandInfoList[0].ModuleName); + } + else { - var commandInfo = commandList[index] as CommandInfo; - Diagnostics.Assert(commandInfo != null, "Elements should always be CommandInfo"); + index = 0; + } + for (; index < commandInfoList.Count; index++) + { + CommandInfo commandInfo = commandInfoList[index]; if (commandInfo.CommandType == CommandTypes.Application) { - endResults.Add(GetCommandNameCompletionResult(commandInfo.Definition, commandInfo, addAmpersandIfNecessary, quote)); + results.Add(GetCommandNameCompletionResult(commandInfo.Definition, commandInfo, addAmpersandIfNecessary, quote)); } - else if (!string.IsNullOrEmpty(commandInfo.ModuleName)) + else if (!string.IsNullOrEmpty(commandInfo.ModuleName) && modulesWithCommand.Add(commandInfo.ModuleName)) { var name = commandInfo.ModuleName + "\\" + commandInfo.Name; - endResults.Add(GetCommandNameCompletionResult(name, commandInfo, addAmpersandIfNecessary, quote)); + results.Add(GetCommandNameCompletionResult(name, commandInfo, addAmpersandIfNecessary, quote)); } } } @@ -398,11 +420,6 @@ internal static List<CompletionResult> MakeCommandsUnique(IEnumerable<PSObject> } } - if (endResults != null && endResults.Count > 0) - { - results.AddRange(endResults); - } - return results; } @@ -423,16 +440,34 @@ public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst fun internal static List<CompletionResult> CompleteModuleName(CompletionContext context, bool loadedModulesOnly, bool skipEditionCheck = false) { - var moduleName = context.WordToComplete ?? string.Empty; + var wordToComplete = context.WordToComplete ?? string.Empty; var result = new List<CompletionResult>(); - var quote = HandleDoubleAndSingleQuote(ref moduleName); + var quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref wordToComplete); + + // Indicates if we should search for modules where the last part of the name matches the input text + // 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 (!moduleName.EndsWith('*')) + if (!wordToComplete.EndsWith('*')) + { + wordToComplete += "*"; + } + + string[] moduleNames; + WildcardPattern shortNamePattern; + if (shortNameSearch) + { + moduleNames = new string[] { wordToComplete, "*." + wordToComplete }; + shortNamePattern = new WildcardPattern(wordToComplete, WildcardOptions.IgnoreCase); + } + else { - moduleName += "*"; + moduleNames = new string[] { wordToComplete }; + shortNamePattern = null; } - var powershell = context.Helper.AddCommandWithPreferenceSetting("Get-Module", typeof(GetModuleCommand)).AddParameter("Name", moduleName); + var powershell = context.Helper.AddCommandWithPreferenceSetting("Get-Module", typeof(GetModuleCommand)).AddParameter("Name", moduleNames); if (!loadedModulesOnly) { powershell.AddParameter("ListAvailable", true); @@ -444,32 +479,58 @@ internal static List<CompletionResult> CompleteModuleName(CompletionContext cont } } - Exception exceptionThrown; - var psObjects = context.Helper.ExecuteCurrentPowerShell(out exceptionThrown); + Collection<PSObject> psObjects = context.Helper.ExecuteCurrentPowerShell(out _); if (psObjects != null) { - foreach (dynamic moduleInfo in psObjects) + // When PowerShell is used interactively, completion is usually triggered by PSReadLine, with PSReadLine's SessionState + // as the engine session state. In that case, results from the module search may contain a nested module of PSReadLine, + // which should be filtered out below. + // When the completion is triggered from global session state, such as when running 'TabExpansion2' from command line, + // the module associated with engine session state will be null. + // + // Note that, it's intentional to not hard code the name 'PSReadLine' in the change, so that in case the tab completion + // is triggered from within a different module, its nested modules can also be filtered out. + HashSet<PSModuleInfo> nestedModulesToFilterOut = null; + PSModuleInfo currentModule = context.ExecutionContext.EngineSessionState.Module; + if (loadedModulesOnly && currentModule?.NestedModules.Count > 0) { - var completionText = moduleInfo.Name.ToString(); - var listItemText = completionText; - var toolTip = "Description: " + moduleInfo.Description.ToString() + "\r\nModuleType: " - + moduleInfo.ModuleType.ToString() + "\r\nPath: " - + moduleInfo.Path.ToString(); + nestedModulesToFilterOut = new(currentModule.NestedModules); + } - if (CompletionRequiresQuotes(completionText, false)) + var completedModules = new HashSet<string>(StringComparer.OrdinalIgnoreCase); + foreach (PSObject item in psObjects) + { + var moduleInfo = (PSModuleInfo)item.BaseObject; + var completionText = moduleInfo.Name; + if (!completedModules.Add(completionText)) { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - completionText = completionText.Replace("'", "''"); - completionText = quoteInUse + completionText + quoteInUse; + continue; } - else + + if (shortNameSearch + && completionText.Contains('.') + && !shortNamePattern.IsMatch(completionText.Substring(completionText.LastIndexOf('.') + 1)) + && !shortNamePattern.IsMatch(completionText)) { - completionText = quote + completionText + quote; + // This check is to make sure we don't return a module whose name only matches the user specified word in the middle. + // For example, when user completes with 'gmo power', we should not return 'Microsoft.PowerShell.Utility'. + continue; } - result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, toolTip)); + if (nestedModulesToFilterOut is not null + && nestedModulesToFilterOut.Contains(moduleInfo)) + { + continue; + } + + var toolTip = "Description: " + moduleInfo.Description + "\r\nModuleType: " + + moduleInfo.ModuleType.ToString() + "\r\nPath: " + + moduleInfo.Path; + + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); + + result.Add(new CompletionResult(completionText, listItemText: moduleInfo.Name, CompletionResultType.ParameterValue, toolTip)); } } @@ -541,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; @@ -608,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) { @@ -615,7 +681,8 @@ private static List<CompletionResult> GetParameterCompletionResults(string param parameterName, bindingInfo.ValidParameterSetsFlags, bindingInfo.UnboundParameters, - withColon); + withColon, + commandAssembly); return result; } @@ -637,7 +704,8 @@ private static List<CompletionResult> GetParameterCompletionResults(string param parameterName, bindingInfo.ValidParameterSetsFlags, bindingInfo.UnboundParameters, - withColon); + withColon, + commandAssembly); } return result; @@ -652,7 +720,8 @@ private static List<CompletionResult> GetParameterCompletionResults(string param parameterName, bindingInfo.ValidParameterSetsFlags, bindingInfo.BoundParameters.Values, - withColon); + withColon, + commandAssembly); } return result; @@ -716,7 +785,8 @@ private static List<CompletionResult> GetParameterCompletionResults(string param parameterName, bindingInfo.ValidParameterSetsFlags, bindingInfo.UnboundParameters, - withColon); + withColon, + commandAssembly); return result; } @@ -724,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 @@ -742,7 +826,7 @@ private static List<CompletionResult> GetParameterCompletionResults(string param $"-{alias}{colonSuffix}", alias, CompletionResultType.ParameterName, - parameterType + alias)); + $"{parameterType}{alias}{helpMessage}")); } } } @@ -750,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> @@ -757,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>(); @@ -778,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; @@ -793,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)); } @@ -1742,7 +1874,7 @@ private static void ProcessParameter( RemoveLastNullCompletionResult(result); string wordToComplete = context.WordToComplete ?? string.Empty; - string quote = HandleDoubleAndSingleQuote(ref wordToComplete); + string quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref wordToComplete); var pattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase); var setList = new List<string>(); @@ -1777,23 +1909,8 @@ private static void ProcessParameter( { string realEntry = entry; string completionText = entry; - if (quote == string.Empty) - { - if (CompletionRequiresQuotes(entry, false)) - { - realEntry = CodeGeneration.EscapeSingleQuotedStringContent(entry); - completionText = "'" + realEntry + "'"; - } - } - else - { - if (quote.Equals("'", StringComparison.OrdinalIgnoreCase)) - { - realEntry = CodeGeneration.EscapeSingleQuotedStringContent(entry); - } - completionText = quote + realEntry + quote; - } + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); result.Add(new CompletionResult(completionText, entry, CompletionResultType.ParameterValue, entry)); } @@ -1819,7 +1936,7 @@ private static void ProcessParameter( } string wordToComplete = context.WordToComplete ?? string.Empty; - string quote = HandleDoubleAndSingleQuote(ref wordToComplete); + string quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref wordToComplete); var pattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase); var enumList = new List<string>(); @@ -2147,6 +2264,12 @@ private static void NativeCommandArgumentCompletion( break; } + if (parameterName.Equals("ExcludeModule", StringComparison.OrdinalIgnoreCase)) + { + NativeCompletionGetCommand(context, moduleName: null, parameterName, result); + break; + } + if (parameterName.Equals("Name", StringComparison.OrdinalIgnoreCase)) { var moduleNames = NativeCommandArgumentCompletion_ExtractSecondaryArgument(boundArguments, "Module"); @@ -2381,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]); } @@ -2514,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) { @@ -2527,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; @@ -2545,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, @@ -2572,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 @@ -2593,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); } @@ -3001,7 +3158,7 @@ private static void NativeCompletionCimNamespace( continue; } - if (!(namespaceNameProperty.Value is string childNamespace)) + if (namespaceNameProperty.Value is not string childNamespace) { continue; } @@ -3046,7 +3203,9 @@ private static void NativeCompletionGetCommand(CompletionContext context, string result.Add(CompletionResult.Null); } - else if (!string.IsNullOrEmpty(paramName) && paramName.Equals("Module", StringComparison.OrdinalIgnoreCase)) + else if (!string.IsNullOrEmpty(paramName) + && (paramName.Equals("Module", StringComparison.OrdinalIgnoreCase) + || paramName.Equals("ExcludeModule", StringComparison.OrdinalIgnoreCase))) { CompleteModule(context, result); } @@ -3123,7 +3282,7 @@ private static void NativeCompletionEventLogCommands(CompletionContext context, RemoveLastNullCompletionResult(result); var logName = context.WordToComplete ?? string.Empty; - var quote = HandleDoubleAndSingleQuote(ref logName); + var quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref logName); if (!logName.EndsWith('*')) { @@ -3145,17 +3304,7 @@ private static void NativeCompletionEventLogCommands(CompletionContext context, var completionText = eventLog.Log.ToString(); var listItemText = completionText; - if (CompletionRequiresQuotes(completionText, false)) - { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - completionText = completionText.Replace("'", "''"); - completionText = quoteInUse + completionText + quoteInUse; - } - else - { - completionText = quote + completionText + quote; - } + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); if (pattern.IsMatch(listItemText)) { @@ -3174,7 +3323,7 @@ private static void NativeCompletionJobCommands(CompletionContext context, strin return; var wordToComplete = context.WordToComplete ?? string.Empty; - var quote = HandleDoubleAndSingleQuote(ref wordToComplete); + var quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref wordToComplete); if (!wordToComplete.EndsWith('*')) { @@ -3236,17 +3385,7 @@ private static void NativeCompletionJobCommands(CompletionContext context, strin var completionText = psJob.Name; var listItemText = completionText; - if (CompletionRequiresQuotes(completionText, false)) - { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - completionText = completionText.Replace("'", "''"); - completionText = quoteInUse + completionText + quoteInUse; - } - else - { - completionText = quote + completionText + quote; - } + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText)); } @@ -3261,7 +3400,7 @@ private static void NativeCompletionScheduledJobCommands(CompletionContext conte return; var wordToComplete = context.WordToComplete ?? string.Empty; - var quote = HandleDoubleAndSingleQuote(ref wordToComplete); + var quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref wordToComplete); if (!wordToComplete.EndsWith('*')) { @@ -3311,17 +3450,7 @@ private static void NativeCompletionScheduledJobCommands(CompletionContext conte var completionText = psJob.Name; var listItemText = completionText; - if (CompletionRequiresQuotes(completionText, false)) - { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - completionText = completionText.Replace("'", "''"); - completionText = quoteInUse + completionText + quoteInUse; - } - else - { - completionText = quote + completionText + quote; - } + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText)); } @@ -3395,7 +3524,7 @@ private static void NativeCompletionProcessCommands(CompletionContext context, s return; var wordToComplete = context.WordToComplete ?? string.Empty; - var quote = HandleDoubleAndSingleQuote(ref wordToComplete); + var quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref wordToComplete); if (!wordToComplete.EndsWith('*')) { @@ -3451,17 +3580,7 @@ private static void NativeCompletionProcessCommands(CompletionContext context, s continue; uniqueSet.Add(completionText); - if (CompletionRequiresQuotes(completionText, false)) - { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - completionText = completionText.Replace("'", "''"); - completionText = quoteInUse + completionText + quoteInUse; - } - else - { - completionText = quote + completionText + quote; - } + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); // on macOS, system processes names will be empty if PowerShell isn't run as `sudo` if (string.IsNullOrEmpty(listItemText)) @@ -3486,7 +3605,7 @@ private static void NativeCompletionProviderCommands(CompletionContext context, RemoveLastNullCompletionResult(result); var providerName = context.WordToComplete ?? string.Empty; - var quote = HandleDoubleAndSingleQuote(ref providerName); + var quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref providerName); if (!providerName.EndsWith('*')) { @@ -3504,17 +3623,7 @@ private static void NativeCompletionProviderCommands(CompletionContext context, var completionText = providerInfo.Name; var listItemText = completionText; - if (CompletionRequiresQuotes(completionText, false)) - { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - completionText = completionText.Replace("'", "''"); - completionText = quoteInUse + completionText + quoteInUse; - } - else - { - completionText = quote + completionText + quote; - } + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText)); } @@ -3530,7 +3639,7 @@ private static void NativeCompletionDriveCommands(CompletionContext context, str RemoveLastNullCompletionResult(result); var wordToComplete = context.WordToComplete ?? string.Empty; - var quote = HandleDoubleAndSingleQuote(ref wordToComplete); + var quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref wordToComplete); if (!wordToComplete.EndsWith('*')) { @@ -3552,17 +3661,7 @@ private static void NativeCompletionDriveCommands(CompletionContext context, str var completionText = driveInfo.Name; var listItemText = completionText; - if (CompletionRequiresQuotes(completionText, false)) - { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - completionText = completionText.Replace("'", "''"); - completionText = quoteInUse + completionText + quoteInUse; - } - else - { - completionText = quote + completionText + quote; - } + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText)); } @@ -3577,7 +3676,7 @@ private static void NativeCompletionServiceCommands(CompletionContext context, s return; var wordToComplete = context.WordToComplete ?? string.Empty; - var quote = HandleDoubleAndSingleQuote(ref wordToComplete); + var quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref wordToComplete); if (!wordToComplete.EndsWith('*')) { @@ -3603,17 +3702,7 @@ private static void NativeCompletionServiceCommands(CompletionContext context, s var completionText = serviceInfo.DisplayName; var listItemText = completionText; - if (CompletionRequiresQuotes(completionText, false)) - { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - completionText = completionText.Replace("'", "''"); - completionText = quoteInUse + completionText + quoteInUse; - } - else - { - completionText = quote + completionText + quote; - } + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText)); } @@ -3634,17 +3723,7 @@ private static void NativeCompletionServiceCommands(CompletionContext context, s var completionText = serviceInfo.Name; var listItemText = completionText; - if (CompletionRequiresQuotes(completionText, false)) - { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - completionText = completionText.Replace("'", "''"); - completionText = quoteInUse + completionText + quoteInUse; - } - else - { - completionText = quote + completionText + quote; - } + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText)); } @@ -3664,7 +3743,7 @@ private static void NativeCompletionVariableCommands(CompletionContext context, RemoveLastNullCompletionResult(result); var variableName = context.WordToComplete ?? string.Empty; - var quote = HandleDoubleAndSingleQuote(ref variableName); + var quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref variableName); if (!variableName.EndsWith('*')) { variableName += "*"; @@ -3690,7 +3769,7 @@ private static void NativeCompletionVariableCommands(CompletionContext context, completionText = completionText.Replace("*", "`*"); } - if (!completionText.Equals("$", StringComparison.Ordinal) && CompletionRequiresQuotes(completionText, false)) + if (!completionText.Equals("$", StringComparison.Ordinal) && CompletionHelpers.CompletionRequiresQuotes(completionText)) { var quoteInUse = effectiveQuote == string.Empty ? "'" : effectiveQuote; if (quoteInUse == "'") @@ -3723,7 +3802,7 @@ private static void NativeCompletionAliasCommands(CompletionContext context, str if (paramName.Equals("Name", StringComparison.OrdinalIgnoreCase)) { var commandName = context.WordToComplete ?? string.Empty; - var quote = HandleDoubleAndSingleQuote(ref commandName); + var quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref commandName); if (!commandName.EndsWith('*')) { @@ -3740,17 +3819,7 @@ private static void NativeCompletionAliasCommands(CompletionContext context, str var completionText = aliasInfo.Name; var listItemText = completionText; - if (CompletionRequiresQuotes(completionText, false)) - { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - completionText = completionText.Replace("'", "''"); - completionText = quoteInUse + completionText + quoteInUse; - } - else - { - completionText = quote + completionText + quote; - } + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText)); } @@ -3784,7 +3853,7 @@ private static void NativeCompletionTraceSourceCommands(CompletionContext contex RemoveLastNullCompletionResult(result); var traceSourceName = context.WordToComplete ?? string.Empty; - var quote = HandleDoubleAndSingleQuote(ref traceSourceName); + var quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref traceSourceName); if (!traceSourceName.EndsWith('*')) { @@ -3803,17 +3872,7 @@ private static void NativeCompletionTraceSourceCommands(CompletionContext contex var completionText = trace.Name; var listItemText = completionText; - if (CompletionRequiresQuotes(completionText, false)) - { - var quoteInUse = quote == string.Empty ? "'" : quote; - if (quoteInUse == "'") - completionText = completionText.Replace("'", "''"); - completionText = quoteInUse + completionText + quoteInUse; - } - else - { - completionText = quote + completionText + quote; - } + completionText = CompletionHelpers.QuoteCompletionText(completionText, quote); result.Add(new CompletionResult(completionText, listItemText, CompletionResultType.ParameterValue, listItemText)); } @@ -4418,16 +4477,17 @@ internal static IEnumerable<CompletionResult> CompleteFilename(CompletionContext internal static IEnumerable<CompletionResult> CompleteFilename(CompletionContext context, bool containerOnly, HashSet<string> extension) { var wordToComplete = context.WordToComplete; - var quote = HandleDoubleAndSingleQuote(ref wordToComplete); + var quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref wordToComplete); - // First, try to match \\server\share - // support both / and \ when entering UNC paths for typing convenience (#17111) - var shareMatch = Regex.Match(wordToComplete, @"^(?:\\\\|//)([^\\/]+)(?:\\|/)([^\\/]*)$"); + // Matches file shares with and without the provider name and with either slash direction. + // Avoids matching Windows device paths like \\.\CDROM0 and \\?\Volume{b8f3fc1c-5cd6-4553-91e2-d6814c4cd375}\ + var shareMatch = s_shareMatch.Match(wordToComplete); if (shareMatch.Success) { // Only match share names, no filenames. - var server = shareMatch.Groups[1].Value; - var sharePattern = WildcardPattern.Get(shareMatch.Groups[2].Value + "*", WildcardOptions.IgnoreCase); + var provider = shareMatch.Groups[1].Value; + var server = shareMatch.Groups[2].Value; + var sharePattern = WildcardPattern.Get(shareMatch.Groups[3].Value + "*", WildcardOptions.IgnoreCase); var ignoreHidden = context.GetOption("IgnoreHiddenShares", @default: false); var shares = GetFileShares(server, ignoreHidden); if (shares.Count == 0) @@ -4440,13 +4500,20 @@ internal static IEnumerable<CompletionResult> CompleteFilename(CompletionContext { if (sharePattern.IsMatch(share)) { - string shareFullPath = "\\\\" + server + "\\" + share; - if (quote != string.Empty) + string sharePath = $"\\\\{server}\\{share}"; + string completionText; + if (quote == string.Empty) + { + completionText = share.Contains(' ') + ? $"'{provider}{sharePath}'" + : $"{provider}{sharePath}"; + } + else { - shareFullPath = quote + shareFullPath + quote; + completionText = $"{quote}{provider}{sharePath}{quote}"; } - shareResults.Add(new CompletionResult(shareFullPath, shareFullPath, CompletionResultType.ProviderContainer, shareFullPath)); + shareResults.Add(new CompletionResult(completionText, share, CompletionResultType.ProviderContainer, sharePath)); } } @@ -4527,20 +4594,41 @@ internal static IEnumerable<CompletionResult> CompleteFilename(CompletionContext basePath = EscapePath(basePath, stringType, useLiteralPath, out _); } - _ = context.Helper + PowerShell currentPS = context.Helper .AddCommandWithPreferenceSetting("Microsoft.PowerShell.Management\\Resolve-Path") .AddParameter("Path", basePath); - var resolvedPaths = context.Helper.ExecuteCurrentPowerShell(out _); - if (resolvedPaths is null || resolvedPaths.Count == 0) + string relativeBasePath; + var useRelativePath = context.GetOption("RelativePaths", @default: defaultRelativePath); + if (useRelativePath) { - return CommandCompletion.EmptyCompletionResult; + if (providerSeparatorIndex != -1) + { + // User must have requested relative paths but that's not valid with provider paths. + return CommandCompletion.EmptyCompletionResult; + } + + 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) + { + relativeBasePath = Directory.GetParent(lastAst.Extent.File).FullName; + _ = currentPS.AddParameter("RelativeBasePath", relativeBasePath); + } + else + { + relativeBasePath = context.ExecutionContext.SessionState.Internal.CurrentLocation.ProviderPath; + } + } + else + { + relativeBasePath = string.Empty; } - var useRelativePath = context.GetOption("RelativePaths", @default: defaultRelativePath); - if (useRelativePath && providerSeparatorIndex != -1) + var resolvedPaths = context.Helper.ExecuteCurrentPowerShell(out _); + if (resolvedPaths is null || resolvedPaths.Count == 0) { - // User must have requested relative paths but that's not valid with provider paths. return CommandCompletion.EmptyCompletionResult; } @@ -4574,7 +4662,8 @@ internal static IEnumerable<CompletionResult> CompleteFilename(CompletionContext useLiteralPath, inputUsedHomeChar, providerPrefix, - stringType); + stringType, + relativeBasePath); break; default: @@ -4609,6 +4698,7 @@ internal static IEnumerable<CompletionResult> CompleteFilename(CompletionContext /// <param name="inputUsedHome"></param> /// <param name="providerPrefix"></param> /// <param name="stringType"></param> + /// <param name="relativeBasePath"></param> /// <returns></returns> private static List<CompletionResult> GetFileSystemProviderResults( CompletionContext context, @@ -4621,7 +4711,8 @@ private static List<CompletionResult> GetFileSystemProviderResults( bool literalPaths, bool inputUsedHome, string providerPrefix, - StringConstantType stringType) + StringConstantType stringType, + string relativeBasePath) { #if DEBUG Diagnostics.Assert(provider.Name.Equals(FileSystemProvider.ProviderName), "Provider should be filesystem provider."); @@ -4696,7 +4787,7 @@ private static List<CompletionResult> GetFileSystemProviderResults( { basePath = context.ExecutionContext.EngineSessionState.NormalizeRelativePath( entry.FullName, - context.ExecutionContext.SessionState.Internal.CurrentLocation.ProviderPath); + relativeBasePath); if (!basePath.StartsWith($"..{provider.ItemSeparator}", StringComparison.Ordinal)) { basePath = $".{provider.ItemSeparator}{basePath}"; @@ -4709,7 +4800,7 @@ private static List<CompletionResult> GetFileSystemProviderResults( var resultType = isContainer ? CompletionResultType.ProviderContainer : CompletionResultType.ProviderItem; - + bool leafQuotesNeeded; var completionText = NewPathCompletionText( basePath, @@ -4776,6 +4867,13 @@ private static List<CompletionResult> GetDefaultProviderResults( bool hadErrors; var childItemOutput = context.Helper.ExecuteCurrentPowerShell(out _, out hadErrors); + if (childItemOutput.Count == 1 && + (pathInfo.Provider.FullName + "::" + pathInfo.ProviderPath).EqualsOrdinalIgnoreCase(childItemOutput[0].Properties["PSPath"].Value as string)) + { + // Get-ChildItem returned the item itself instead of the children so there must be no child items to complete. + continue; + } + var childrenInfoTable = new Dictionary<string, bool>(childItemOutput.Count); var childNameList = new List<string>(childItemOutput.Count); @@ -4811,7 +4909,7 @@ private static List<CompletionResult> GetDefaultProviderResults( if (childNameList.Count == 0) { - return results; + continue; } string basePath = providerPrefix.Length > 0 @@ -4926,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('~'); @@ -5049,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; } @@ -5097,6 +5205,10 @@ private static string NewPathCompletionText(string parent, string leaf, StringCo return result; } + private static readonly Regex s_shareMatch = new( + @"(^Microsoft\.PowerShell\.Core\\FileSystem::|^FileSystem::|^)(?:\\\\|//)(?![.|?])([^\\/]+)(?:\\|/)([^\\/]*)$", + RegexOptions.IgnoreCase | RegexOptions.Compiled); + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct SHARE_INFO_1 { @@ -5120,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 } @@ -5186,11 +5308,12 @@ public static IEnumerable<CompletionResult> CompleteVariable(string variableName return CompleteVariable(new CompletionContext { WordToComplete = variableName, Helper = helper, ExecutionContext = executionContext }); } - private static readonly string[] s_variableScopes = new string[] { "Global:", "Local:", "Script:", "Private:" }; + private static readonly string[] s_variableScopes = new string[] { "Global:", "Local:", "Script:", "Private:", "Using:" }; - private static readonly char[] s_charactersRequiringQuotes = new char[] { - '-', '`', '&', '@', '\'', '"', '#', '{', '}', '(', ')', '$', ',', ';', '|', '<', '>', ' ', '.', '\\', '/', '\t', '^', - }; + private static readonly SearchValues<char> s_charactersRequiringQuotes = SearchValues.Create("-`&@'\"#{}()$,;|<> .\\/ \t^"); + + private static bool ContainsCharactersRequiringQuotes(ReadOnlySpan<char> text) + => text.ContainsAny(s_charactersRequiringQuotes); internal static List<CompletionResult> CompleteVariable(CompletionContext context) { @@ -5199,7 +5322,13 @@ internal static List<CompletionResult> CompleteVariable(CompletionContext contex List<CompletionResult> tempResults = new(); var wordToComplete = context.WordToComplete; + string scopePrefix = string.Empty; var colon = wordToComplete.IndexOf(':'); + if (colon >= 0) + { + scopePrefix = wordToComplete.Remove(colon + 1); + wordToComplete = wordToComplete.Substring(colon + 1); + } var lastAst = context.RelatedAsts?[^1]; var variableAst = lastAst as VariableExpressionAst; @@ -5242,15 +5371,23 @@ internal static List<CompletionResult> CompleteVariable(CompletionContext contex continue; } - var varInfo = findVariablesVisitor.VariableInfoTable[varName]; - var varType = varInfo.LastDeclaredConstraint ?? varInfo.LastAssignedType; - var toolTip = varType is null - ? varName - : StringUtil.Format("[{0}]${1}", ToStringCodeMethods.Type(varType, dropNamespaces: true), varName); + VariableInfo varInfo = findVariablesVisitor.VariableInfoTable[varName]; + PSTypeName varType = varInfo.LastDeclaredConstraint ?? varInfo.LastAssignedType; + string toolTip; + if (varType is null) + { + toolTip = varName; + } + else + { + toolTip = varType.Type is not null + ? StringUtil.Format("[{0}]${1}", ToStringCodeMethods.Type(varType.Type, dropNamespaces: true), varName) + : varType.Name; + } - var completionText = !tokenAtCursorUsedBraces && varName.IndexOfAny(s_charactersRequiringQuotes) == -1 - ? prefix + varName - : prefix + "{" + varName + "}"; + var completionText = !tokenAtCursorUsedBraces && !ContainsCharactersRequiringQuotes(varName) + ? prefix + scopePrefix + varName + : prefix + "{" + scopePrefix + varName + "}"; AddUniqueVariable(hashedResults, results, completionText, varName, toolTip); } } @@ -5268,10 +5405,15 @@ internal static List<CompletionResult> CompleteVariable(CompletionContext contex var toolTip = value is null ? key : StringUtil.Format("[{0}]${1}", ToStringCodeMethods.Type(value.GetType(), dropNamespaces: true), key); - var completionText = !tokenAtCursorUsedBraces && name.IndexOfAny(s_charactersRequiringQuotes) == -1 + if (!string.IsNullOrEmpty(variable.Description)) + { + toolTip += $" - {variable.Description}"; + } + + var completionText = !tokenAtCursorUsedBraces && !ContainsCharactersRequiringQuotes(name) ? prefix + name : prefix + "{" + name + "}"; - AddUniqueVariable(hashedResults, tempResults, completionText, key, key); + AddUniqueVariable(hashedResults, tempResults, completionText, key, toolTip); } } @@ -5283,15 +5425,14 @@ internal static List<CompletionResult> CompleteVariable(CompletionContext contex } else { - string provider = wordToComplete.Substring(0, colon + 1); string pattern; - if (s_variableScopes.Contains(provider, StringComparer.OrdinalIgnoreCase)) + if (s_variableScopes.Contains(scopePrefix, StringComparer.OrdinalIgnoreCase)) { - pattern = string.Concat("variable:", wordToComplete.AsSpan(colon + 1), "*"); + pattern = string.Concat("variable:", wordToComplete, "*"); } else { - pattern = wordToComplete + "*"; + pattern = scopePrefix + wordToComplete + "*"; } var powerShellExecutionHelper = context.Helper; @@ -5319,11 +5460,16 @@ internal static List<CompletionResult> CompleteVariable(CompletionContext contex ToStringCodeMethods.Type(value.GetType(), dropNamespaces: true), name); } + + if (!string.IsNullOrEmpty(variable.Description)) + { + tooltip += $" - {variable.Description}"; + } } - var completedName = !tokenAtCursorUsedBraces && name.IndexOfAny(s_charactersRequiringQuotes) == -1 - ? prefix + provider + name - : prefix + "{" + provider + name + "}"; + var completedName = !tokenAtCursorUsedBraces && !ContainsCharactersRequiringQuotes(name) + ? prefix + scopePrefix + name + : prefix + "{" + scopePrefix + name + "}"; AddUniqueVariable(hashedResults, results, completedName, name, tooltip); } } @@ -5336,7 +5482,7 @@ internal static List<CompletionResult> CompleteVariable(CompletionContext contex foreach (var key in envVars.Keys) { var name = "env:" + key; - var completedName = !tokenAtCursorUsedBraces && name.IndexOfAny(s_charactersRequiringQuotes) == -1 + var completedName = !tokenAtCursorUsedBraces && !ContainsCharactersRequiringQuotes(name) ? prefix + name : prefix + "{" + name + "}"; AddUniqueVariable(hashedResults, tempResults, completedName, name, "[string]" + name); @@ -5346,22 +5492,22 @@ internal static List<CompletionResult> CompleteVariable(CompletionContext contex tempResults.Clear(); } - // Return variables already in session state first, because we can sometimes give better information, - // like the variables type. - foreach (var specialVariable in s_specialVariablesCache.Value) + if (colon == -1) { - if (wildcardPattern.IsMatch(specialVariable)) + // Return variables already in session state first, because we can sometimes give better information, + // like the variables type. + foreach (var specialVariable in s_specialVariablesCache.Value) { - var completedName = !tokenAtCursorUsedBraces && specialVariable.IndexOfAny(s_charactersRequiringQuotes) == -1 - ? prefix + specialVariable - : prefix + "{" + specialVariable + "}"; + if (wildcardPattern.IsMatch(specialVariable)) + { + var completedName = !tokenAtCursorUsedBraces && !ContainsCharactersRequiringQuotes(specialVariable) + ? prefix + specialVariable + : prefix + "{" + specialVariable + "}"; - AddUniqueVariable(hashedResults, results, completedName, specialVariable, specialVariable); + AddUniqueVariable(hashedResults, results, completedName, specialVariable, specialVariable); + } } - } - if (colon == -1) - { var allDrives = context.ExecutionContext.SessionState.Drive.GetAll(); foreach (var drive in allDrives) { @@ -5372,7 +5518,7 @@ internal static List<CompletionResult> CompleteVariable(CompletionContext contex continue; } - var completedName = !tokenAtCursorUsedBraces && drive.Name.IndexOfAny(s_charactersRequiringQuotes) == -1 + var completedName = !tokenAtCursorUsedBraces && !ContainsCharactersRequiringQuotes(drive.Name) ? prefix + drive.Name + ":" : prefix + "{" + drive.Name + ":}"; var tooltip = string.IsNullOrEmpty(drive.Description) @@ -5390,7 +5536,7 @@ internal static List<CompletionResult> CompleteVariable(CompletionContext contex { if (wildcardPattern.IsMatch(scope)) { - var completedName = !tokenAtCursorUsedBraces && scope.IndexOfAny(s_charactersRequiringQuotes) == -1 + var completedName = !tokenAtCursorUsedBraces && !ContainsCharactersRequiringQuotes(scope) ? prefix + scope : prefix + "{" + scope + "}"; AddUniqueVariable(hashedResults, results, completedName, scope, scope); @@ -5409,7 +5555,7 @@ private static void AddUniqueVariable(HashSet<string> hashedResults, List<Comple } } - private static readonly HashSet<string> s_varModificationCommands = new(StringComparer.OrdinalIgnoreCase) + internal static readonly HashSet<string> s_varModificationCommands = new(StringComparer.OrdinalIgnoreCase) { "New-Variable", "nv", @@ -5418,13 +5564,13 @@ private static void AddUniqueVariable(HashSet<string> hashedResults, List<Comple "sv" }; - private static readonly string[] s_varModificationParameters = new string[] + internal static readonly string[] s_varModificationParameters = new string[] { "Name", "Value" }; - private static readonly string[] s_outVarParameters = new string[] + internal static readonly string[] s_outVarParameters = new string[] { "ErrorVariable", "ev", @@ -5437,13 +5583,13 @@ private static void AddUniqueVariable(HashSet<string> hashedResults, List<Comple }; - private static readonly string[] s_pipelineVariableParameters = new string[] + internal static readonly string[] s_pipelineVariableParameters = new string[] { "PipelineVariable", "pv" }; - private static readonly HashSet<string> s_localScopeCommandNames = new(StringComparer.OrdinalIgnoreCase) + internal static readonly HashSet<string> s_localScopeCommandNames = new(StringComparer.OrdinalIgnoreCase) { "Microsoft.PowerShell.Core\\ForEach-Object", "ForEach-Object", @@ -5459,11 +5605,11 @@ private static void AddUniqueVariable(HashSet<string> hashedResults, List<Comple private sealed class VariableInfo { - internal Type LastDeclaredConstraint; - internal Type LastAssignedType; + internal PSTypeName LastDeclaredConstraint; + internal PSTypeName LastAssignedType; } - private sealed class FindVariablesVisitor : AstVisitor + private sealed class FindVariablesVisitor : AstVisitor2 { internal Ast Top; internal Ast CompletionVariableAst; @@ -5472,34 +5618,34 @@ private sealed class FindVariablesVisitor : AstVisitor internal int StopSearchOffset; internal TypeInferenceContext Context; - private static Type GetInferredVarTypeFromAst(Ast ast) + private static PSTypeName GetInferredVarTypeFromAst(Ast ast) { - Type type; + PSTypeName type; switch (ast) { case ConstantExpressionAst constant: - type = constant.StaticType; + type = new PSTypeName(constant.StaticType); break; case ExpandableStringExpressionAst: - type = typeof(string); + type = new PSTypeName(typeof(string)); break; case ConvertExpressionAst convertExpression: - type = convertExpression.StaticType; + type = new PSTypeName(convertExpression.Type.TypeName); break; case HashtableAst: - type = typeof(Hashtable); + type = new PSTypeName(typeof(Hashtable)); break; case ArrayExpressionAst: case ArrayLiteralAst: - type = typeof(object[]); + type = new PSTypeName(typeof(object[])); break; case ScriptBlockExpressionAst: - type = typeof(ScriptBlock); + type = new PSTypeName(typeof(ScriptBlock)); break; default: @@ -5510,10 +5656,9 @@ private static Type GetInferredVarTypeFromAst(Ast ast) return type; } - private void SaveVariableInfo(string variableName, Type variableType, bool isConstraint) + private void SaveVariableInfo(string variableName, PSTypeName variableType, bool isConstraint) { - VariableInfo varInfo; - if (VariableInfoTable.TryGetValue(variableName, out varInfo)) + if (VariableInfoTable.TryGetValue(variableName, out VariableInfo varInfo)) { if (isConstraint) { @@ -5538,7 +5683,11 @@ public override AstVisitAction DefaultVisit(Ast ast) { if (ast.Extent.StartOffset > StopSearchOffset) { - return AstVisitAction.StopVisit; + // When visiting do while/until statements, the condition will be visited before the statement block. + // The condition itself may not be interesting if it's after the cursor, but the statement block could be. + return ast is PipelineBaseAst && ast.Parent is DoUntilStatementAst or DoWhileStatementAst + ? AstVisitAction.SkipChildren + : AstVisitAction.StopVisit; } return AstVisitAction.Continue; @@ -5548,30 +5697,62 @@ public override AstVisitAction VisitAssignmentStatement(AssignmentStatementAst a { if (assignmentStatementAst.Extent.StartOffset > StopSearchOffset) { - return AstVisitAction.StopVisit; + return assignmentStatementAst.Parent is DoUntilStatementAst or DoWhileStatementAst ? + AstVisitAction.SkipChildren + : AstVisitAction.StopVisit; } - if (assignmentStatementAst.Left is ConvertExpressionAst convertExpression) + ProcessAssignmentLeftSide(assignmentStatementAst.Left, assignmentStatementAst.Right); + return AstVisitAction.Continue; + } + + private void ProcessAssignmentLeftSide(ExpressionAst left, StatementAst right) + { + if (left is AttributedExpressionAst attributedExpression) { - if (convertExpression.Child is VariableExpressionAst variableExpression) + var firstConvertExpression = attributedExpression as ConvertExpressionAst; + ExpressionAst child = attributedExpression.Child; + while (child is AttributedExpressionAst attributeChild) + { + if (firstConvertExpression is null && attributeChild is ConvertExpressionAst convertExpression) + { + // Multiple type constraint can be set on a variable like this: [int] [string] $Var1 = 1 + // But it's the left most type constraint that determines the final type. + firstConvertExpression = convertExpression; + } + + child = attributeChild.Child; + } + + if (child is VariableExpressionAst variableExpression) { if (variableExpression == CompletionVariableAst || s_specialVariablesCache.Value.Contains(variableExpression.VariablePath.UserPath)) { - return AstVisitAction.Continue; + return; } - SaveVariableInfo(variableExpression.VariablePath.UserPath, convertExpression.StaticType, isConstraint: true); + if (firstConvertExpression is not null) + { + SaveVariableInfo(variableExpression.VariablePath.UnqualifiedPath, new PSTypeName(firstConvertExpression.Type.TypeName), isConstraint: true); + } + else + { + 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; } - Type lastAssignedType; - if (assignmentStatementAst.Right is CommandExpressionAst commandExpression) + PSTypeName lastAssignedType; + if (right is CommandExpressionAst commandExpression) { lastAssignedType = GetInferredVarTypeFromAst(commandExpression.Expression); } @@ -5580,10 +5761,23 @@ public override AstVisitAction VisitAssignmentStatement(AssignmentStatementAst a lastAssignedType = null; } - SaveVariableInfo(variableExpression.VariablePath.UserPath, lastAssignedType, isConstraint: false); + SaveVariableInfo(variableExpression.VariablePath.UnqualifiedPath, lastAssignedType, isConstraint: false); + } + 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); + } } - - return AstVisitAction.Continue; } public override AstVisitAction VisitCommand(CommandAst commandAst) @@ -5603,7 +5797,7 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) var nameValue = variableName.ConstantValue as string; if (nameValue is not null) { - Type variableType; + PSTypeName variableType; if (bindingResult.BoundParameters.TryGetValue("Value", out ParameterBindingResult variableValue)) { variableType = GetInferredVarTypeFromAst(variableValue.Value); @@ -5628,7 +5822,7 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) var varName = outVarBind.ConstantValue as string; if (varName is not null) { - SaveVariableInfo(varName, typeof(ArrayList), isConstraint: false); + SaveVariableInfo(varName, new PSTypeName(typeof(ArrayList)), isConstraint: false); } } } @@ -5643,9 +5837,9 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) if (varName is not null) { var inferredTypes = AstTypeInference.InferTypeOf(commandAst, Context, TypeInferenceRuntimePermissions.AllowSafeEval); - Type varType = inferredTypes.Count == 0 + PSTypeName varType = inferredTypes.Count == 0 ? null - : inferredTypes[0].Type; + : inferredTypes[0]; SaveVariableInfo(varName, varType, isConstraint: false); } } @@ -5653,6 +5847,46 @@ public override AstVisitAction VisitCommand(CommandAst commandAst) } } + foreach (RedirectionAst redirection in commandAst.Redirections) + { + if (redirection is FileRedirectionAst fileRedirection + && fileRedirection.Location is StringConstantExpressionAst redirectTarget + && redirectTarget.Value.StartsWith("variable:", StringComparison.OrdinalIgnoreCase) + && redirectTarget.Value.Length > "variable:".Length) + { + string varName = redirectTarget.Value.Substring("variable:".Length); + PSTypeName varType; + switch (fileRedirection.FromStream) + { + case RedirectionStream.Error: + varType = new PSTypeName(typeof(ErrorRecord)); + break; + + case RedirectionStream.Warning: + varType = new PSTypeName(typeof(WarningRecord)); + break; + + case RedirectionStream.Verbose: + varType = new PSTypeName(typeof(VerboseRecord)); + break; + + case RedirectionStream.Debug: + varType = new PSTypeName(typeof(DebugRecord)); + break; + + case RedirectionStream.Information: + varType = new PSTypeName(typeof(InformationRecord)); + break; + + default: + varType = null; + break; + } + + SaveVariableInfo(varName, varType, isConstraint: false); + } + } + return AstVisitAction.Continue; } @@ -5669,7 +5903,7 @@ public override AstVisitAction VisitParameter(ParameterAst parameterAst) return AstVisitAction.Continue; } - SaveVariableInfo(variableExpression.VariablePath.UserPath, parameterAst.StaticType, isConstraint: true); + SaveVariableInfo(variableExpression.VariablePath.UnqualifiedPath, new PSTypeName(parameterAst.StaticType), isConstraint: true); return AstVisitAction.Continue; } @@ -5681,7 +5915,7 @@ public override AstVisitAction VisitForEachStatement(ForEachStatementAst forEach return AstVisitAction.StopVisit; } - SaveVariableInfo(forEachStatementAst.Variable.VariablePath.UserPath, variableType: null, isConstraint: false); + SaveVariableInfo(forEachStatementAst.Variable.VariablePath.UnqualifiedPath, variableType: null, isConstraint: false); return AstVisitAction.Continue; } @@ -5741,7 +5975,7 @@ public override AstVisitAction VisitDataStatement(DataStatementAst dataStatement } } - private static readonly Lazy<SortedSet<string>> s_specialVariablesCache = new Lazy<SortedSet<string>>(BuildSpecialVariablesCache); + private static readonly Lazy<SortedSet<string>> s_specialVariablesCache = new(BuildSpecialVariablesCache); private static SortedSet<string> BuildSpecialVariablesCache() { @@ -5757,6 +5991,35 @@ private static SortedSet<string> BuildSpecialVariablesCache() return result; } + internal static PSTypeName GetLastDeclaredTypeConstraint(VariableExpressionAst variableAst, TypeInferenceContext typeInferenceContext) + { + Ast parent = variableAst.Parent; + var findVariablesVisitor = new FindVariablesVisitor() + { + CompletionVariableAst = variableAst, + StopSearchOffset = variableAst.Extent.StartOffset, + Context = typeInferenceContext + }; + while (parent != null) + { + if (parent is IParameterMetadataProvider) + { + findVariablesVisitor.Top = parent; + parent.Visit(findVariablesVisitor); + } + + if (findVariablesVisitor.VariableInfoTable.TryGetValue(variableAst.VariablePath.UserPath, out VariableInfo varInfo) + && varInfo.LastDeclaredConstraint is not null) + { + return varInfo.LastDeclaredConstraint; + } + + parent = parent.Parent; + } + + return null; + } + #endregion Variables #region Comments @@ -5824,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)) @@ -5874,12 +6137,13 @@ private static List<CompletionResult> CompleteRequires(CompletionContext context // Produce completions for all parameters that begin with the prefix we've found, // but which haven't already been specified in the line we need to complete - foreach (KeyValuePair<string, string> parameter in s_requiresParameters) + foreach (string parameter in s_requiresParameters) { - if (parameter.Key.StartsWith(currentParameterPrefix, StringComparison.OrdinalIgnoreCase) - && !context.CursorPosition.Line.Contains($" -{parameter.Key}", StringComparison.OrdinalIgnoreCase)) + if (parameter.StartsWith(currentParameterPrefix, StringComparison.OrdinalIgnoreCase) + && !context.CursorPosition.Line.Contains($" -{parameter}", StringComparison.OrdinalIgnoreCase)) { - results.Add(new CompletionResult(parameter.Key, parameter.Key, CompletionResultType.ParameterName, parameter.Value)); + string toolTip = GetRequiresParametersToolTip(parameter); + results.Add(new CompletionResult(parameter, parameter, CompletionResultType.ParameterName, toolTip)); } } @@ -5904,11 +6168,12 @@ private static List<CompletionResult> CompleteRequires(CompletionContext context // Complete PSEdition parameter values if (currentParameterMatch.Value.Equals("PSEdition", StringComparison.OrdinalIgnoreCase)) { - foreach (KeyValuePair<string, string> psEditionEntry in s_requiresPSEditions) + foreach (string psEditionEntry in s_requiresPSEditions) { - if (psEditionEntry.Key.StartsWith(currentValue, StringComparison.OrdinalIgnoreCase)) + if (psEditionEntry.StartsWith(currentValue, StringComparison.OrdinalIgnoreCase)) { - results.Add(new CompletionResult(psEditionEntry.Key, psEditionEntry.Key, CompletionResultType.ParameterValue, psEditionEntry.Value)); + string toolTip = GetRequiresPsEditionsToolTip(psEditionEntry); + results.Add(new CompletionResult(psEditionEntry, psEditionEntry, CompletionResultType.ParameterValue, toolTip)); } } @@ -5936,7 +6201,7 @@ private static List<CompletionResult> CompleteRequires(CompletionContext context hashtableKeyMatches = Regex.Matches(hashtableString, @"(@{|;)\s*(?:'|\""|\w*)\w*"); // Build the list of keys we might want to complete, based on what's already been provided - var moduleSpecKeysToComplete = new HashSet<string>(s_requiresModuleSpecKeys.Keys); + var moduleSpecKeysToComplete = new HashSet<string>(s_requiresModuleSpecKeys); bool sawModuleNameLast = false; foreach (Match existingHashtableKeyMatch in hashtableKeyMatches) { @@ -5992,7 +6257,8 @@ private static List<CompletionResult> CompleteRequires(CompletionContext context { if (moduleSpecKey.StartsWith(currentValue, StringComparison.OrdinalIgnoreCase)) { - results.Add(new CompletionResult(moduleSpecKey, moduleSpecKey, CompletionResultType.ParameterValue, s_requiresModuleSpecKeys[moduleSpecKey])); + string toolTip = GetRequiresModuleSpecKeysToolTip(moduleSpecKey); + results.Add(new CompletionResult(moduleSpecKey, moduleSpecKey, CompletionResultType.ParameterValue, toolTip)); } } } @@ -6000,27 +6266,53 @@ private static List<CompletionResult> CompleteRequires(CompletionContext context return results; } - private static readonly IReadOnlyDictionary<string, string> s_requiresParameters = new SortedList<string, string>(StringComparer.OrdinalIgnoreCase) + private static readonly string[] s_requiresParameters = new string[] + { + "Modules", + "PSEdition", + "RunAsAdministrator", + "Version" + }; + + private static string GetRequiresParametersToolTip(string name) => name switch + { + "Modules" => TabCompletionStrings.RequiresModulesParameterDescription, + "PSEdition" => TabCompletionStrings.RequiresPSEditionParameterDescription, + "RunAsAdministrator" => TabCompletionStrings.RequiresRunAsAdministratorParameterDescription, + "Version" => TabCompletionStrings.RequiresVersionParameterDescription, + _ => string.Empty + }; + + private static readonly string[] s_requiresPSEditions = new string[] { - { "Modules", "Specifies PowerShell modules that the script requires." }, - { "PSEdition", "Specifies a PowerShell edition that the script requires." }, - { "RunAsAdministrator", "Specifies that PowerShell must be running as administrator on Windows." }, - { "Version", "Specifies the minimum version of PowerShell that the script requires." }, + "Core", + "Desktop" }; - private static readonly IReadOnlyDictionary<string, string> s_requiresPSEditions = new SortedList<string, string>(StringComparer.OrdinalIgnoreCase) + private static string GetRequiresPsEditionsToolTip(string name) => name switch { - { "Core", "Specifies that the script requires PowerShell Core to run." }, - { "Desktop", "Specifies that the script requires Windows PowerShell to run." }, + "Core" => TabCompletionStrings.RequiresPsEditionCoreDescription, + "Desktop" => TabCompletionStrings.RequiresPsEditionDesktopDescription, + _ => string.Empty }; - private static readonly IReadOnlyDictionary<string, string> s_requiresModuleSpecKeys = new SortedList<string, string>(StringComparer.OrdinalIgnoreCase) + private static readonly string[] s_requiresModuleSpecKeys = new string[] { - { "ModuleName", "Required. Specifies the module name." }, - { "GUID", "Optional. Specifies the GUID of the module." }, - { "ModuleVersion", "Specifies a minimum acceptable version of the module." }, - { "RequiredVersion", "Specifies an exact, required version of the module." }, - { "MaximumVersion", "Specifies the maximum acceptable version of the module." }, + "GUID", + "MaximumVersion", + "ModuleName", + "ModuleVersion", + "RequiredVersion" + }; + + private static string GetRequiresModuleSpecKeysToolTip(string name) => name switch + { + "GUID" => TabCompletionStrings.RequiresModuleSpecGUIDDescription, + "MaximumVersion" => TabCompletionStrings.RequiresModuleSpecMaximumVersionDescription, + "ModuleName" => TabCompletionStrings.RequiresModuleSpecModuleNameDescription, + "ModuleVersion" => TabCompletionStrings.RequiresModuleSpecModuleVersionDescription, + "RequiredVersion" => TabCompletionStrings.RequiresModuleSpecRequiredVersionDescription, + _ => string.Empty }; private static readonly char[] s_hashtableKeyPrefixes = new[] @@ -6065,7 +6357,7 @@ private static List<CompletionResult> CompleteCommentHelp(CompletionContext cont replacementIndex = context.TokenAtCursor.Extent.StartOffset + lineKeyword.Index; replacementLength = lineKeyword.Value.Length; - var validKeywords = new HashSet<String>(s_commentHelpKeywords.Keys, StringComparer.OrdinalIgnoreCase); + var validKeywords = new HashSet<string>(s_commentHelpKeywords, StringComparer.OrdinalIgnoreCase); foreach (Match keyword in usedKeywords) { if (keyword == lineKeyword || s_commentHelpAllowedDuplicateKeywords.Contains(keyword.Value)) @@ -6081,7 +6373,8 @@ private static List<CompletionResult> CompleteCommentHelp(CompletionContext cont { if (keyword.StartsWith(lineKeyword.Value, StringComparison.OrdinalIgnoreCase)) { - result.Add(new CompletionResult(keyword, keyword, CompletionResultType.Keyword, s_commentHelpKeywords[keyword])); + string toolTip = GetCommentHelpKeywordsToolTip(keyword); + result.Add(new CompletionResult(keyword, keyword, CompletionResultType.Keyword, toolTip)); } } @@ -6141,46 +6434,66 @@ private static List<CompletionResult> CompleteCommentHelp(CompletionContext cont return null; } - private static readonly IReadOnlyDictionary<string, string> s_commentHelpKeywords = new SortedList<string, string>(StringComparer.OrdinalIgnoreCase) - { - { "SYNOPSIS", "A brief description of the function or script. This keyword can be used only once in each topic." }, - { "DESCRIPTION", "A detailed description of the function or script. This keyword can be used only once in each topic." }, - { "PARAMETER", ".PARAMETER <Parameter-Name>\nThe description of a parameter. Add a .PARAMETER keyword for each parameter in the function or script syntax." }, - { "EXAMPLE", "A sample command that uses the function or script, optionally followed by sample output and a description. Repeat this keyword for each example." }, - { "INPUTS", "The .NET types of objects that can be piped to the function or script. You can also include a description of the input objects." }, - { "OUTPUTS", "The .NET type of the objects that the cmdlet returns. You can also include a description of the returned objects." }, - { "NOTES", "Additional information about the function or script." }, - { "LINK", "The name of a related topic. Repeat the .LINK keyword for each related topic. The .Link keyword content can also include a URI to an online version of the same help topic." }, - { "COMPONENT", "The name of the technology or feature that the function or script uses, or to which it is related." }, - { "ROLE", "The name of the user role for the help topic." }, - { "FUNCTIONALITY", "The keywords that describe the intended use of the function." }, - { "FORWARDHELPTARGETNAME", ".FORWARDHELPTARGETNAME <Command-Name>\nRedirects to the help topic for the specified command." }, - { "FORWARDHELPCATEGORY", ".FORWARDHELPCATEGORY <Category>\nSpecifies the help category of the item in .ForwardHelpTargetName" }, - { "REMOTEHELPRUNSPACE", ".REMOTEHELPRUNSPACE <PSSession-variable>\nSpecifies a session that contains the help topic. Enter a variable that contains a PSSession object." }, - { "EXTERNALHELP", ".EXTERNALHELP <XML Help File>\nThe .ExternalHelp keyword is required when a function or script is documented in XML files." } + private static readonly string[] s_commentHelpKeywords = new string[] + { + "COMPONENT", + "DESCRIPTION", + "EXAMPLE", + "EXTERNALHELP", + "FORWARDHELPCATEGORY", + "FORWARDHELPTARGETNAME", + "FUNCTIONALITY", + "INPUTS", + "LINK", + "NOTES", + "OUTPUTS", + "PARAMETER", + "REMOTEHELPRUNSPACE", + "ROLE", + "SYNOPSIS" + }; + + private static string GetCommentHelpKeywordsToolTip(string name) => name switch + { + "COMPONENT" => TabCompletionStrings.CommentHelpCOMPONENTKeywordDescription, + "DESCRIPTION" => TabCompletionStrings.CommentHelpDESCRIPTIONKeywordDescription, + "EXAMPLE" => TabCompletionStrings.CommentHelpEXAMPLEKeywordDescription, + "EXTERNALHELP" => TabCompletionStrings.CommentHelpEXTERNALHELPKeywordDescription, + "FORWARDHELPCATEGORY" => TabCompletionStrings.CommentHelpFORWARDHELPCATEGORYKeywordDescription, + "FORWARDHELPTARGETNAME" => TabCompletionStrings.CommentHelpFORWARDHELPTARGETNAMEKeywordDescription, + "FUNCTIONALITY" => TabCompletionStrings.CommentHelpFUNCTIONALITYKeywordDescription, + "INPUTS" => TabCompletionStrings.CommentHelpINPUTSKeywordDescription, + "LINK" => TabCompletionStrings.CommentHelpLINKKeywordDescription, + "NOTES" => TabCompletionStrings.CommentHelpNOTESKeywordDescription, + "OUTPUTS" => TabCompletionStrings.CommentHelpOUTPUTSKeywordDescription, + "PARAMETER" => TabCompletionStrings.CommentHelpPARAMETERKeywordDescription, + "REMOTEHELPRUNSPACE" => TabCompletionStrings.CommentHelpREMOTEHELPRUNSPACEKeywordDescription, + "ROLE" => TabCompletionStrings.CommentHelpROLEKeywordDescription, + "SYNOPSIS" => TabCompletionStrings.CommentHelpSYNOPSISKeywordDescription, + _ => string.Empty }; private static readonly HashSet<string> s_commentHelpAllowedDuplicateKeywords = new(StringComparer.OrdinalIgnoreCase) { - "PARAMETER", "EXAMPLE", - "LINK" + "LINK", + "PARAMETER" }; private static readonly string[] s_commentHelpForwardCategories = new string[] { "Alias", + "All", "Cmdlet", - "HelpFile", + "ExternalScript", + "FAQ", + "Filter", "Function", - "Provider", "General", - "FAQ", "Glossary", - "ScriptCommand", - "ExternalScript", - "Filter", - "All" + "HelpFile", + "Provider", + "ScriptCommand" }; private static FunctionDefinitionAst GetCommentHelpFunctionTarget(CompletionContext context) @@ -6290,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 @@ -6696,7 +7011,7 @@ private static void CompleteFormatViewByInferredType(CompletionContext context, Diagnostics.Assert(controlBodyType is not null, "This should never happen unless a new Format-* cmdlet is added"); var wordToComplete = context.WordToComplete; - var quote = HandleDoubleAndSingleQuote(ref wordToComplete); + var quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref wordToComplete); WildcardPattern viewPattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase); var uniqueNames = new HashSet<string>(); @@ -6715,7 +7030,7 @@ private static void CompleteFormatViewByInferredType(CompletionContext context, { string completionText = viewDefinition.name; // If the string is quoted or if it contains characters that need quoting, quote it in single quotes - if (quote != string.Empty || viewDefinition.name.IndexOfAny(s_charactersRequiringQuotes) != -1) + if (quote != string.Empty || ContainsCharactersRequiringQuotes(viewDefinition.name)) { completionText = "'" + completionText.Replace("'", "''") + "'"; } @@ -6877,7 +7192,7 @@ private static void AddInferredMember(object member, WildcardPattern memberNameP { completionText = $"{memberName}("; } - else if (memberName.IndexOfAny(s_charactersRequiringQuotes) != -1) + else if (ContainsCharactersRequiringQuotes(memberName)) { completionText = $"'{memberName}'"; } @@ -7615,44 +7930,34 @@ private static string GetNamespaceToRemove(CompletionContext context, TypeComple internal static List<CompletionResult> CompleteHelpTopics(CompletionContext context) { - var results = new List<CompletionResult>(); - string userHelpDir = HelpUtils.GetUserHomeHelpSearchPath(); - string appHelpDir = Utils.GetApplicationBase(Utils.DefaultPowerShellShellID); - string currentCulture = CultureInfo.CurrentCulture.Name; - - //search for help files for the current culture + en-US as fallback - var searchPaths = new string[] + ArrayList helpProviders = context.ExecutionContext.HelpSystem.HelpProviders; + HelpFileHelpProvider helpFileProvider = null; + for (int i = helpProviders.Count - 1; i >= 0; i--) { - Path.Combine(userHelpDir, currentCulture), - Path.Combine(appHelpDir, currentCulture), - Path.Combine(userHelpDir, "en-US"), - Path.Combine(appHelpDir, "en-US") - }.Distinct(); + if (helpProviders[i] is HelpFileHelpProvider provider) + { + helpFileProvider = provider; + break; + } + } - string wordToComplete = context.WordToComplete + "*"; - try + if (helpFileProvider is null) { - var wildcardPattern = WildcardPattern.Get(wordToComplete, WildcardOptions.IgnoreCase); + return null; + } - foreach (var dir in searchPaths) + List<CompletionResult> results = new(); + Collection<string> filesMatched = MUIFileSearcher.SearchFiles($"{context.WordToComplete}*.help.txt", helpFileProvider.GetExtendedSearchPaths()); + foreach (string path in filesMatched) + { + string fileName = Path.GetFileName(path); + if (fileName.StartsWith("about_", StringComparison.OrdinalIgnoreCase)) { - var currentDir = new DirectoryInfo(dir); - if (currentDir.Exists) - { - foreach (var file in currentDir.EnumerateFiles("about_*.help.txt")) - { - if (wildcardPattern.IsMatch(file.Name)) - { - string topicName = file.Name.Substring(0, file.Name.LastIndexOf(".help.txt")); - results.Add(new CompletionResult(topicName)); - } - } - } + string topicName = fileName.Substring(0, fileName.Length - ".help.txt".Length); + results.Add(new CompletionResult(topicName, topicName, CompletionResultType.ParameterValue, topicName)); } } - catch (Exception) - { - } + return results; } @@ -7671,9 +7976,7 @@ internal static List<CompletionResult> CompleteStatementFlags(TokenKind kind, st bool withColon = wordToComplete.EndsWith(':'); wordToComplete = withColon ? wordToComplete.Remove(wordToComplete.Length - 1) : wordToComplete; - string enumString = LanguagePrimitives.EnumSingleTypeConverter.EnumValues(typeof(SwitchFlags)); - string separator = CultureInfo.CurrentUICulture.TextInfo.ListSeparator; - string[] enumArray = enumString.Split(separator, StringSplitOptions.RemoveEmptyEntries); + string[] enumArray = LanguagePrimitives.EnumSingleTypeConverter.GetEnumNames(typeof(SwitchFlags)); var pattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase); var enumList = new List<string>(); @@ -7963,7 +8266,7 @@ internal static List<CompletionResult> CompleteHashtableKey(CompletionContext co } var result = new List<CompletionResult>(); - foreach (var key in s_requiresModuleSpecKeys.Keys) + foreach (string key in s_requiresModuleSpecKeys) { if (excludedKeys.Contains(key) || (wordToComplete is not null && !key.StartsWith(wordToComplete, StringComparison.OrdinalIgnoreCase)) @@ -7972,7 +8275,10 @@ internal static List<CompletionResult> CompleteHashtableKey(CompletionContext co { continue; } - result.Add(new CompletionResult(key, key, CompletionResultType.Property, s_requiresModuleSpecKeys[key])); + + string toolTip = GetRequiresModuleSpecKeysToolTip(key); + + result.Add(new CompletionResult(key, key, CompletionResultType.Property, toolTip)); } return result; @@ -8224,7 +8530,9 @@ private static List<CompletionResult> GetSpecialHashTableKeyMembers(HashSet<stri { if ((string.IsNullOrEmpty(wordToComplete) || key.StartsWith(wordToComplete, StringComparison.OrdinalIgnoreCase)) && !excludedKeys.Contains(key)) { - result.Add(new CompletionResult(key, key, CompletionResultType.Property, key)); + string toolTip = GetHashtableKeyDescriptionToolTip(key); + + result.Add(new CompletionResult(key, key, CompletionResultType.Property, toolTip)); } } @@ -8236,6 +8544,31 @@ private static List<CompletionResult> GetSpecialHashTableKeyMembers(HashSet<stri return result; } + private static string GetHashtableKeyDescriptionToolTip(string name) => name switch + { + "Alignment" => TabCompletionStrings.AlignmentHashtableKeyDescription, + "Ascending" => TabCompletionStrings.AscendingHashtableKeyDescription, + "Data" => TabCompletionStrings.DataHashtableKeyDescription, + "Depth" => TabCompletionStrings.DepthHashtableKeyDescription, + "Descending" => TabCompletionStrings.DescendingHashtableKeyDescription, + "EndTime" => TabCompletionStrings.EndTimeHashtableKeyDescription, + "Expression" => TabCompletionStrings.ExpressionHashtableKeyDescription, + "FormatString" => TabCompletionStrings.FormatStringHashtableKeyDescription, + "ID" => TabCompletionStrings.IDHashtableKeyDescription, + "Keywords" => TabCompletionStrings.KeywordsHashtableKeyDescription, + "Label" => TabCompletionStrings.LabelHashtableKeyDescription, + "Level" => TabCompletionStrings.LevelHashtableKeyDescription, + "LogName" => TabCompletionStrings.LogNameHashtableKeyDescription, + "Name" => TabCompletionStrings.NameHashtableKeyDescription, + "Path" => TabCompletionStrings.PathHashtableKeyDescription, + "ProviderName" => TabCompletionStrings.ProviderNameHashtableKeyDescription, + "StartTime" => TabCompletionStrings.StartTimeHashtableKeyDescription, + "SuppressHashFilter" => TabCompletionStrings.SuppressHashFilterHashtableKeyDescription, + "UserID" => TabCompletionStrings.UserIDHashtableKeyDescription, + "Width" => TabCompletionStrings.WidthHashtableKeyDescription, + _ => string.Empty + }; + #endregion Hashtable Keys #region Helpers @@ -8255,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) @@ -8316,36 +8649,31 @@ internal static string CombineVariableWithPartialPath(VariableExpressionAst vari return null; } - internal static string HandleDoubleAndSingleQuote(ref string wordToComplete) + /// <summary> + /// Calls Get-Command to get command info objects. + /// </summary> + /// <param name="fakeBoundParameters">The fake bound parameters.</param> + /// <param name="parametersToAdd">The parameters to add.</param> + /// <returns>Collection of command info objects.</returns> + internal static Collection<CommandInfo> GetCommandInfo( + IDictionary fakeBoundParameters, + params string[] parametersToAdd) { - string quote = string.Empty; + using var ps = PowerShell.Create(RunspaceMode.CurrentRunspace); - if (!string.IsNullOrEmpty(wordToComplete) && (wordToComplete[0].IsSingleQuote() || wordToComplete[0].IsDoubleQuote())) - { - char frontQuote = wordToComplete[0]; - int length = wordToComplete.Length; + ps.AddCommand("Get-Command"); - if (length == 1) - { - wordToComplete = string.Empty; - quote = frontQuote.IsSingleQuote() ? "'" : "\""; - } - else if (length > 1) + foreach (string parameter in parametersToAdd) + { + if (fakeBoundParameters.Contains(parameter)) { - if ((wordToComplete[length - 1].IsDoubleQuote() && frontQuote.IsDoubleQuote()) || (wordToComplete[length - 1].IsSingleQuote() && frontQuote.IsSingleQuote())) - { - wordToComplete = wordToComplete.Substring(1, length - 2); - quote = frontQuote.IsSingleQuote() ? "'" : "\""; - } - else if (!wordToComplete[length - 1].IsDoubleQuote() && !wordToComplete[length - 1].IsSingleQuote()) - { - wordToComplete = wordToComplete.Substring(1); - quote = frontQuote.IsSingleQuote() ? "'" : "\""; - } + ps.AddParameter(parameter, fakeBoundParameters[parameter]); } } - return quote; + Collection<CommandInfo> commands = ps.Invoke<CommandInfo>(); + + return commands; } internal static bool IsSplattedVariable(Ast targetExpr) @@ -8388,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; } @@ -8413,7 +8741,7 @@ internal static void CompleteMemberHelper( var completionText = memberInfo.Name; // Handle scenarios like this: $aa | add-member 'a b' 23; $aa.a<tab> - if (completionText.IndexOfAny(s_charactersRequiringQuotes) != -1) + if (ContainsCharactersRequiringQuotes(completionText)) { completionText = completionText.Replace("'", "''"); completionText = "'" + completionText + "'"; @@ -8454,13 +8782,13 @@ 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)) { // Handle scenarios like this: $hashtable["abc#d"] = 100; $hashtable.ab<tab> - if (key.IndexOfAny(s_charactersRequiringQuotes) != -1) + if (ContainsCharactersRequiringQuotes(key)) { key = key.Replace("'", "''"); key = "'" + key + "'"; @@ -8518,32 +8846,6 @@ private static bool IsStaticTypeEnumerable(Type type) return false; } - private static bool CompletionRequiresQuotes(string completion, bool escape) - { - // If the tokenizer sees the completion as more than two tokens, or if there is some error, then - // some form of quoting is necessary (if it's a variable, we'd need ${}, filenames would need [], etc.) - - Language.Token[] tokens; - ParseError[] errors; - Language.Parser.ParseInput(completion, out tokens, out errors); - - char[] charToCheck = escape ? new[] { '$', '[', ']', '`' } : new[] { '$', '`' }; - - // Expect no errors and 2 tokens (1 is for our completion, the other is eof) - // Or if the completion is a keyword, we ignore the errors - bool requireQuote = !(errors.Length == 0 && tokens.Length == 2); - if ((!requireQuote && tokens[0] is StringToken) || - (tokens.Length == 2 && (tokens[0].TokenFlags & TokenFlags.Keyword) != 0)) - { - requireQuote = false; - var value = tokens[0].Text; - if (value.IndexOfAny(charToCheck) != -1) - requireQuote = true; - } - - return requireQuote; - } - private static bool ProviderSpecified(string path) { var index = path.IndexOf(':'); diff --git a/src/System.Management.Automation/engine/CommandCompletion/CompletionHelpers.cs b/src/System.Management.Automation/engine/CommandCompletion/CompletionHelpers.cs new file mode 100644 index 00000000000..edf31e8e97a --- /dev/null +++ b/src/System.Management.Automation/engine/CommandCompletion/CompletionHelpers.cs @@ -0,0 +1,322 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Buffers; +using System.Collections.Generic; +using System.Management.Automation.Language; + +namespace System.Management.Automation +{ + /// <summary> + /// Shared helper class for common completion helper methods. + /// </summary> + 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="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> + /// <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, + CompletionDisplayInfoMapper displayInfoMapper = null, + CompletionResultType resultType = CompletionResultType.Text, + MatchStrategy matchStrategy = null) + { + displayInfoMapper ??= DefaultDisplayInfoMapper; + matchStrategy ??= DefaultMatch; + + string quote = HandleDoubleAndSingleQuote(ref wordToComplete); + if (quote != SingleQuote) + { + wordToComplete = NormalizeToExpandableString(wordToComplete); + } + + foreach (string value in possibleCompletionValues) + { + if (matchStrategy(value, wordToComplete)) + { + string completionText = QuoteCompletionText(value, quote); + + (string toolTip, string listItemText) = displayInfoMapper(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> + /// <param name="wordToComplete"> + /// The string to process, potentially surrounded by single or double quotes. + /// This parameter is updated in-place to exclude the removed quotes. + /// </param> + /// <returns> + /// The type of quote detected (single or double), or an empty string if no quote is found. + /// </returns> + /// <remarks> + /// This method checks for single or double quotes at the start and end of the string. + /// If wrapping quotes are detected and match, both are removed; otherwise, only the front quote is removed. + /// The string is updated in-place, and only matching front-and-back quotes are stripped. + /// If no quotes are detected or the input is empty, the original string remains unchanged. + /// </remarks> + internal static string HandleDoubleAndSingleQuote(ref string wordToComplete) + { + if (string.IsNullOrEmpty(wordToComplete)) + { + return string.Empty; + } + + char frontQuote = wordToComplete[0]; + bool hasFrontSingleQuote = frontQuote.IsSingleQuote(); + bool hasFrontDoubleQuote = frontQuote.IsDoubleQuote(); + + if (!hasFrontSingleQuote && !hasFrontDoubleQuote) + { + return string.Empty; + } + + string quoteInUse = hasFrontSingleQuote ? SingleQuote : DoubleQuote; + + int length = wordToComplete.Length; + if (length == 1) + { + wordToComplete = string.Empty; + return quoteInUse; + } + + char backQuote = wordToComplete[length - 1]; + bool hasBackSingleQuote = backQuote.IsSingleQuote(); + bool hasBackDoubleQuote = backQuote.IsDoubleQuote(); + + bool hasBothFrontAndBackQuotes = + (hasFrontSingleQuote && hasBackSingleQuote) || (hasFrontDoubleQuote && hasBackDoubleQuote); + + if (hasBothFrontAndBackQuotes) + { + wordToComplete = wordToComplete.Substring(1, length - 2); + return quoteInUse; + } + + bool hasFrontQuoteAndNoBackQuote = + (hasFrontSingleQuote || hasFrontDoubleQuote) && !hasBackSingleQuote && !hasBackDoubleQuote; + + if (hasFrontQuoteAndNoBackQuote) + { + wordToComplete = wordToComplete.Substring(1); + return quoteInUse; + } + + return string.Empty; + } + + /// <summary> + /// Determines whether the specified completion string requires quotes. + /// Quoting is required if: + /// <list type="bullet"> + /// <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> + /// <returns><c>true</c> if the string requires quotes, <c>false</c> otherwise.</returns> + internal static bool CompletionRequiresQuotes(string completion) + { + Parser.ParseInput(completion, out Token[] tokens, out ParseError[] errors); + + bool isExpectedTokenCount = tokens.Length == 2; + + bool requireQuote = errors.Length > 0 || !isExpectedTokenCount; + + 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); + + /// <summary> + /// Quotes a given completion text. + /// </summary> + /// <param name="completionText"> + /// The text to be quoted. + /// </param> + /// <param name="quote"> + /// The quote character to use for enclosing the text. Defaults to a single quote if not provided. + /// </param> + /// <returns> + /// The quoted <paramref name="completionText"/>. + /// </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) ? SingleQuote : quote; + + if (quoteInUse == SingleQuote) + { + completionText = CodeGeneration.EscapeSingleQuotedStringContent(completionText); + } + + return quoteInUse + completionText + quoteInUse; + } + } +} 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/CommandCompletion/PseudoParameterBinder.cs b/src/System.Management.Automation/engine/CommandCompletion/PseudoParameterBinder.cs index 4201be16f93..2e7457cd812 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/PseudoParameterBinder.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/PseudoParameterBinder.cs @@ -1198,7 +1198,7 @@ private bool PrepareCommandElements(ExecutionContext context, CommandParameterAs string commandName = null; try { - processor = PrepareFromAst(context, out commandName) ?? context.CreateCommand(commandName, dotSource); + processor = PrepareFromAst(context, out commandName) ?? context.CreateCommand(commandName, dotSource, forCompletion:true); } catch (RuntimeException) { @@ -1406,7 +1406,6 @@ private CommandProcessorBase PrepareFromAst(ExecutionContext context, out string } ast.Visit(exportVisitor); - CommandProcessorBase commandProcessor = null; resolvedCommandName = _commandAst.GetCommandName(); diff --git a/src/System.Management.Automation/engine/CommandCompletion/ScopeArgumentCompleter.cs b/src/System.Management.Automation/engine/CommandCompletion/ScopeArgumentCompleter.cs index b397be3aa5f..444ccf79adb 100644 --- a/src/System.Management.Automation/engine/CommandCompletion/ScopeArgumentCompleter.cs +++ b/src/System.Management.Automation/engine/CommandCompletion/ScopeArgumentCompleter.cs @@ -29,16 +29,8 @@ public IEnumerable<CompletionResult> CompleteArgument( string wordToComplete, CommandAst commandAst, IDictionary fakeBoundParameters) - { - var scopePattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase); - - foreach (string scope in s_Scopes) - { - if (scopePattern.IsMatch(scope)) - { - yield return new CompletionResult(scope); - } - } - } + => CompletionHelpers.GetMatchingResults( + wordToComplete, + possibleCompletionValues: s_Scopes); } } diff --git a/src/System.Management.Automation/engine/CommandDiscovery.cs b/src/System.Management.Automation/engine/CommandDiscovery.cs index 561a33ccba8..9ca87f4c834 100644 --- a/src/System.Management.Automation/engine/CommandDiscovery.cs +++ b/src/System.Management.Automation/engine/CommandDiscovery.cs @@ -262,6 +262,9 @@ internal void AddSessionStateCmdletEntryToCache(SessionStateCmdletEntry entry, b /// False if not. Null if command discovery should default to something reasonable /// for the command discovered. /// </param> + /// <param name="forCompletion"> + /// True if this for parameter completion and script requirements should be ignored. + /// </param> /// <returns> /// </returns> /// <exception cref="CommandNotFoundException"> @@ -271,14 +274,15 @@ internal void AddSessionStateCmdletEntryToCache(SessionStateCmdletEntry entry, b /// If the security manager is preventing the command from running. /// </exception> internal CommandProcessorBase LookupCommandProcessor(string commandName, - CommandOrigin commandOrigin, bool? useLocalScope) + CommandOrigin commandOrigin, bool? useLocalScope, bool forCompletion = false) { CommandProcessorBase processor = null; CommandInfo commandInfo = LookupCommandInfo(commandName, commandOrigin); if (commandInfo != null) { - processor = LookupCommandProcessor(commandInfo, commandOrigin, useLocalScope, null); + processor = LookupCommandProcessor(commandInfo, commandOrigin, useLocalScope, null, forCompletion); + // commandInfo.Name might be different than commandName - restore the original invocation name processor.Command.MyInvocation.InvocationName = commandName; } @@ -286,7 +290,7 @@ internal CommandProcessorBase LookupCommandProcessor(string commandName, return processor; } - internal static void VerifyRequiredModules(ExternalScriptInfo scriptInfo, ExecutionContext context) + internal static void VerifyRequiredModules(ExternalScriptInfo scriptInfo, ExecutionContext context, bool forCompletion = false) { // Check Required Modules if (scriptInfo.RequiresModules != null) @@ -301,7 +305,7 @@ internal static void VerifyRequiredModules(ExternalScriptInfo scriptInfo, Execut moduleManifestPath: null, manifestProcessingFlags: ModuleCmdletBase.ManifestProcessingFlags.LoadElements | ModuleCmdletBase.ManifestProcessingFlags.WriteErrors, error: out error); - if (error != null) + if (!forCompletion && error is not null) { ScriptRequiresException scriptRequiresException = new ScriptRequiresException( @@ -316,9 +320,9 @@ internal static void VerifyRequiredModules(ExternalScriptInfo scriptInfo, Execut } } - private CommandProcessorBase CreateScriptProcessorForSingleShell(ExternalScriptInfo scriptInfo, ExecutionContext context, bool useLocalScope, SessionStateInternal sessionState) + private CommandProcessorBase CreateScriptProcessorForSingleShell(ExternalScriptInfo scriptInfo, ExecutionContext context, bool useLocalScope, SessionStateInternal sessionState, bool forCompletion = false) { - VerifyScriptRequirements(scriptInfo, Context); + VerifyScriptRequirements(scriptInfo, Context, forCompletion); if (!string.IsNullOrEmpty(scriptInfo.RequiresApplicationID)) { @@ -340,12 +344,18 @@ private CommandProcessorBase CreateScriptProcessorForSingleShell(ExternalScriptI // #Requires -PSVersion // #Requires -PSEdition // #Requires -Module - internal static void VerifyScriptRequirements(ExternalScriptInfo scriptInfo, ExecutionContext context) + internal static void VerifyScriptRequirements(ExternalScriptInfo scriptInfo, ExecutionContext context, bool forCompletion = false) { - VerifyElevatedPrivileges(scriptInfo); - VerifyPSVersion(scriptInfo); - VerifyPSEdition(scriptInfo); - VerifyRequiredModules(scriptInfo, context); + // When completing script parameters we don't care if these requirements are met. + // VerifyRequiredModules will attempt to load the required modules which is useful for completion (so the correct types are loaded). + if (!forCompletion) + { + VerifyElevatedPrivileges(scriptInfo); + VerifyPSVersion(scriptInfo); + VerifyPSEdition(scriptInfo); + } + + VerifyRequiredModules(scriptInfo, context, forCompletion); } internal static void VerifyPSVersion(ExternalScriptInfo scriptInfo) @@ -426,6 +436,9 @@ internal static void VerifyElevatedPrivileges(ExternalScriptInfo scriptInfo) /// False if not. Null if command discovery should default to something reasonable /// for the command discovered. /// </param> + /// <param name="forCompletion"> + /// True if this for parameter completion and script requirements should be ignored. + /// </param> /// <param name="sessionState">The session state the commandInfo should be run in.</param> /// <returns> /// </returns> @@ -436,7 +449,7 @@ internal static void VerifyElevatedPrivileges(ExternalScriptInfo scriptInfo) /// If the security manager is preventing the command from running. /// </exception> internal CommandProcessorBase LookupCommandProcessor(CommandInfo commandInfo, - CommandOrigin commandOrigin, bool? useLocalScope, SessionStateInternal sessionState) + CommandOrigin commandOrigin, bool? useLocalScope, SessionStateInternal sessionState, bool forCompletion = false) { CommandProcessorBase processor = null; @@ -482,7 +495,7 @@ internal CommandProcessorBase LookupCommandProcessor(CommandInfo commandInfo, scriptInfo.SignatureChecked = true; try { - processor = CreateScriptProcessorForSingleShell(scriptInfo, Context, useLocalScope ?? true, sessionState); + processor = CreateScriptProcessorForSingleShell(scriptInfo, Context, useLocalScope ?? true, sessionState, forCompletion); } catch (ScriptRequiresSyntaxException reqSyntaxException) { @@ -1218,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 4bf39d2c1fd..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}", @@ -1448,7 +1448,7 @@ private static CanDoPathLookupResult CanDoPathLookup(string possiblePath) // If the command contains any invalid path characters, we can't // do the path lookup - if (possiblePath.IndexOfAny(Path.GetInvalidPathChars()) != -1) + if (PathUtils.ContainsInvalidPathChars(possiblePath)) { result = CanDoPathLookupResult.IllegalCharacters; break; diff --git a/src/System.Management.Automation/engine/CommonCommandParameters.cs b/src/System.Management.Automation/engine/CommonCommandParameters.cs index ea636dc5838..9dc92d817aa 100644 --- a/src/System.Management.Automation/engine/CommonCommandParameters.cs +++ b/src/System.Management.Automation/engine/CommonCommandParameters.cs @@ -225,7 +225,7 @@ public string OutVariable /// This parameter configures the number of objects to buffer before calling the downstream Cmdlet /// </remarks> [Parameter] - [ValidateRangeAttribute(0, Int32.MaxValue)] + [ValidateRange(0, Int32.MaxValue)] [Alias("ob")] public int OutBuffer { 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/DataStoreAdapter.cs b/src/System.Management.Automation/engine/DataStoreAdapter.cs index 4cbe6f15f5a..07c7cef4d01 100644 --- a/src/System.Management.Automation/engine/DataStoreAdapter.cs +++ b/src/System.Management.Automation/engine/DataStoreAdapter.cs @@ -26,7 +26,7 @@ public class PSDriveInfo : IComparable /// using "SessionState" as the category. /// This is the same category as the SessionState tracer class. /// </summary> - [Dbg.TraceSourceAttribute( + [Dbg.TraceSource( "PSDriveInfo", "The namespace navigation tracer")] private static readonly Dbg.PSTraceSource s_tracer = diff --git a/src/System.Management.Automation/engine/ErrorPackage.cs b/src/System.Management.Automation/engine/ErrorPackage.cs index 8eeec501031..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() @@ -1689,12 +1689,7 @@ public override string ToString() if (Exception != null) { - if (!string.IsNullOrEmpty(Exception.Message)) - { - return Exception.Message; - } - - return Exception.ToString(); + return Exception.Message ?? Exception.ToString(); } return base.ToString(); @@ -1828,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 56f64c1a5c2..ac39eade17b 100644 --- a/src/System.Management.Automation/engine/ExecutionContext.cs +++ b/src/System.Management.Automation/engine/ExecutionContext.cs @@ -477,7 +477,6 @@ internal LocationGlobber LocationGlobber /// The assemblies that have been loaded for this runspace. /// </summary> internal Dictionary<string, Assembly> AssemblyCache { get; private set; } - #endregion Properties #region Engine State @@ -634,12 +633,13 @@ internal HelpSystem HelpSystem /// </summary> /// <param name="command">The name of the command to lookup.</param> /// <param name="dotSource"></param> + /// <param name="forCompletion"></param> /// <returns>The command processor object.</returns> - internal CommandProcessorBase CreateCommand(string command, bool dotSource) + internal CommandProcessorBase CreateCommand(string command, bool dotSource, bool forCompletion = false) { CommandOrigin commandOrigin = this.EngineSessionState.CurrentScope.ScopeOrigin; CommandProcessorBase commandProcessor = - CommandDiscovery.LookupCommandProcessor(command, commandOrigin, !dotSource); + CommandDiscovery.LookupCommandProcessor(command, commandOrigin, !dotSource, forCompletion); // Reset the command origin for script commands... // BUGBUG - dotting can get around command origin checks??? if (commandProcessor != null && commandProcessor is ScriptCommandProcessorBase) { @@ -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/EnableDisableExperimentalFeatureCommand.cs b/src/System.Management.Automation/engine/ExperimentalFeature/EnableDisableExperimentalFeatureCommand.cs index 2cdb266571d..af09f427253 100644 --- a/src/System.Management.Automation/engine/ExperimentalFeature/EnableDisableExperimentalFeatureCommand.cs +++ b/src/System.Management.Automation/engine/ExperimentalFeature/EnableDisableExperimentalFeatureCommand.cs @@ -4,6 +4,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Globalization; using System.Linq; using System.Management.Automation; @@ -112,26 +113,28 @@ public class ExperimentalFeatureNameCompleter : IArgumentCompleter /// <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) + public IEnumerable<CompletionResult> CompleteArgument( + string commandName, + string parameterName, + string wordToComplete, + CommandAst commandAst, + IDictionary fakeBoundParameters) { - if (fakeBoundParameters == null) - { - throw PSTraceSource.NewArgumentNullException(nameof(fakeBoundParameters)); - } - - var commandInfo = new CmdletInfo("Get-ExperimentalFeature", typeof(GetExperimentalFeatureCommand)); - var ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace) - .AddCommand(commandInfo) - .AddParameter("Name", wordToComplete + "*"); + SortedSet<string> expirmentalFeatures = new(StringComparer.OrdinalIgnoreCase); - HashSet<string> names = new HashSet<string>(); - var results = ps.Invoke<ExperimentalFeature>(); - foreach (var result in results) + foreach (ExperimentalFeature feature in GetExperimentalFeatures()) { - names.Add(result.Name); + expirmentalFeatures.Add(feature.Name); } - return names.Order().Select(static name => new CompletionResult(name, name, CompletionResultType.Text, name)); + return CompletionHelpers.GetMatchingResults(wordToComplete, expirmentalFeatures); + } + + private static Collection<ExperimentalFeature> GetExperimentalFeatures() + { + using var ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace); + ps.AddCommand("Get-ExperimentalFeature"); + return ps.Invoke<ExperimentalFeature>(); } } } 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 db6ce736bba..10e8334021b 100644 --- a/src/System.Management.Automation/engine/GetCommandCommand.cs +++ b/src/System.Management.Automation/engine/GetCommandCommand.cs @@ -140,6 +140,28 @@ public string[] Module private string[] _modules = Array.Empty<string>(); private bool _isModuleSpecified = false; + /// <summary> + /// Gets or sets the ExcludeModule parameter to the cmdlet. + /// </summary> + [Parameter()] + public string[] ExcludeModule + { + get + { + return _excludedModules; + } + + set + { + value ??= Array.Empty<string>(); + + _excludedModules = value; + _excludedModulePatterns = null; + } + } + + private string[] _excludedModules = Array.Empty<string>(); + /// <summary> /// Gets or sets the FullyQualifiedModule parameter to the cmdlet. /// </summary> @@ -404,6 +426,7 @@ protected override void ProcessRecord() // Initialize the module patterns _modulePatterns ??= SessionStateUtilities.CreateWildcardsFromStrings(Module, WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant); + _excludedModulePatterns ??= SessionStateUtilities.CreateWildcardsFromStrings(ExcludeModule, WildcardOptions.IgnoreCase | WildcardOptions.CultureInvariant); switch (ParameterSetName) { @@ -702,6 +725,13 @@ private bool IsNounVerbMatch(CommandInfo command) if (!string.IsNullOrEmpty(command.ModuleName)) { + if (_excludedModulePatterns is not null + && _excludedModulePatterns.Count > 0 + && SessionStateUtilities.MatchesAnyWildcardPattern(command.ModuleName, _excludedModulePatterns, true)) + { + break; + } + if (_isFullyQualifiedModuleSpecified) { if (!_moduleSpecifications.Any( @@ -1271,6 +1301,13 @@ private bool IsCommandMatch(ref CommandInfo current, out bool isDuplicate) } else { + if (_excludedModulePatterns is not null + && _excludedModulePatterns.Count > 0 + && SessionStateUtilities.MatchesAnyWildcardPattern(current.ModuleName, _excludedModulePatterns, true)) + { + return false; + } + if (_isFullyQualifiedModuleSpecified) { bool foundModuleMatch = false; @@ -1530,6 +1567,7 @@ private bool IsCommandInResult(CommandInfo command) private Collection<WildcardPattern> _verbPatterns; private Collection<WildcardPattern> _nounPatterns; private Collection<WildcardPattern> _modulePatterns; + private Collection<WildcardPattern> _excludedModulePatterns; #if LEGACYTELEMETRY private Stopwatch _timer = new Stopwatch(); @@ -1656,40 +1694,50 @@ private static PSObject GetParameterType(Type parameterType) } /// <summary> + /// Provides argument completion for Noun parameter. /// </summary> public class NounArgumentCompleter : IArgumentCompleter { + /// </summary> + /// Returns completion results for Noun 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: GetCommandNouns(fakeBoundParameters)); + /// <summary> + /// Get sorted set of command nouns using Get-Command. /// </summary> - public IEnumerable<CompletionResult> CompleteArgument(string commandName, string parameterName, string wordToComplete, CommandAst commandAst, IDictionary fakeBoundParameters) + /// <param name="fakeBoundParameters">The fake bound parameters.</param> + /// <returns>Sorted set of command nouns.</returns> + private static SortedSet<string> GetCommandNouns(IDictionary fakeBoundParameters) { - if (fakeBoundParameters == null) - { - throw PSTraceSource.NewArgumentNullException(nameof(fakeBoundParameters)); - } - - var commandInfo = new CmdletInfo("Get-Command", typeof(GetCommandCommand)); - var ps = System.Management.Automation.PowerShell.Create(RunspaceMode.CurrentRunspace) - .AddCommand(commandInfo) - .AddParameter("Noun", wordToComplete + "*"); - - if (fakeBoundParameters.Contains("Module")) - { - ps.AddParameter("Module", fakeBoundParameters["Module"]); - } + Collection<CommandInfo> commands = CompletionCompleters.GetCommandInfo(fakeBoundParameters, "Module", "Verb"); + SortedSet<string> nouns = new(StringComparer.OrdinalIgnoreCase); - HashSet<string> nouns = new HashSet<string>(); - var results = ps.Invoke<CommandInfo>(); - foreach (var result in results) + foreach (CommandInfo command in commands) { - var dash = result.Name.IndexOf('-'); - if (dash != -1) + string commandName = command.Name; + int dashIndex = commandName.IndexOf('-'); + if (dashIndex != -1) { - nouns.Add(result.Name.Substring(dash + 1)); + string noun = commandName.Substring(dashIndex + 1); + nouns.Add(noun); } } - return nouns.Order().Select(static noun => new CompletionResult(noun, noun, CompletionResultType.Text, noun)); + return nouns; } } } diff --git a/src/System.Management.Automation/engine/InitialSessionState.cs b/src/System.Management.Automation/engine/InitialSessionState.cs index 2af3ac56bad..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(); @@ -2860,7 +2860,7 @@ private string MakeUserNamePath() // Ensure that user name contains no invalid path characters. // MSDN indicates that logon names cannot contain any of these invalid characters, // but this check will ensure safety. - if (userName.IndexOfAny(System.IO.Path.GetInvalidPathChars()) > -1) + if (PathUtils.ContainsInvalidPathChars(userName)) { throw new PSInvalidOperationException(RemotingErrorIdStrings.InvalidUserDriveName); } @@ -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 86502861bfa..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; } @@ -2741,17 +2834,9 @@ public IEnumerable<CompletionResult> CompleteArgument( string wordToComplete, CommandAst commandAst, IDictionary fakeBoundParameters) - { - var strictModeVersionPattern = WildcardPattern.Get(wordToComplete + "*", WildcardOptions.IgnoreCase); - - foreach (string version in s_strictModeVersions) - { - if (strictModeVersionPattern.IsMatch(version)) - { - yield return new CompletionResult(version); - } - } - } + => CompletionHelpers.GetMatchingResults( + wordToComplete, + possibleCompletionValues: s_strictModeVersions); } #endregion Set-StrictMode diff --git a/src/System.Management.Automation/engine/Interop/Windows/AssignProcessToJobObject.cs b/src/System.Management.Automation/engine/Interop/Windows/AssignProcessToJobObject.cs index 1a924fb51dc..7605420dab4 100644 --- a/src/System.Management.Automation/engine/Interop/Windows/AssignProcessToJobObject.cs +++ b/src/System.Management.Automation/engine/Interop/Windows/AssignProcessToJobObject.cs @@ -4,13 +4,16 @@ #nullable enable using System.Runtime.InteropServices; +using Microsoft.Win32.SafeHandles; internal static partial class Interop { internal static partial class Windows { - [LibraryImport("Kernel32.dll", SetLastError = true)] + [LibraryImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] - internal static partial bool AssignProcessToJobObject(nint hJob, nint hProcess); + internal static partial bool AssignProcessToJobObject( + SafeJobHandle hJob, + SafeProcessHandle hProcess); } } diff --git a/src/System.Management.Automation/engine/Interop/Windows/CreateIoCompletionPort.cs b/src/System.Management.Automation/engine/Interop/Windows/CreateIoCompletionPort.cs new file mode 100644 index 00000000000..0b877fcdaea --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/CreateIoCompletionPort.cs @@ -0,0 +1,38 @@ +// 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 + { + internal sealed class SafeIoCompletionPort : SafeHandle + { + public SafeIoCompletionPort() : base(invalidHandleValue: nint.Zero, ownsHandle: true) { } + + public override bool IsInvalid => handle == nint.Zero; + + protected override bool ReleaseHandle() + => Windows.CloseHandle(handle); + } + + [LibraryImport("kernel32.dll", SetLastError = true)] + private static partial SafeIoCompletionPort CreateIoCompletionPort( + nint FileHandle, + nint ExistingCompletionPort, + nint CompletionKey, + int NumberOfConcurrentThreads); + + internal static SafeIoCompletionPort CreateIoCompletionPort() + { + return CreateIoCompletionPort( + FileHandle: -1, + ExistingCompletionPort: nint.Zero, + CompletionKey: nint.Zero, + NumberOfConcurrentThreads: 1); + } + } +} diff --git a/src/System.Management.Automation/engine/Interop/Windows/CreateJobObject.cs b/src/System.Management.Automation/engine/Interop/Windows/CreateJobObject.cs new file mode 100644 index 00000000000..fee16b813aa --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/CreateJobObject.cs @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Windows + { + internal sealed class SafeJobHandle : SafeHandle + { + public SafeJobHandle() : base(invalidHandleValue: nint.Zero, ownsHandle: true) { } + + public override bool IsInvalid => handle == nint.Zero; + + protected override bool ReleaseHandle() + => Windows.CloseHandle(handle); + } + + [LibraryImport("kernel32.dll", EntryPoint = "CreateJobObjectW", SetLastError = true)] + private static partial SafeJobHandle CreateJobObject( + nint lpJobAttributes, + nint lpName); + + internal static SafeJobHandle CreateJobObject() + => CreateJobObject(nint.Zero, nint.Zero); + } +} diff --git a/src/System.Management.Automation/engine/Interop/Windows/GetQueuedCompletionStatus.cs b/src/System.Management.Automation/engine/Interop/Windows/GetQueuedCompletionStatus.cs new file mode 100644 index 00000000000..d23234e850b --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/GetQueuedCompletionStatus.cs @@ -0,0 +1,36 @@ +// 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 + { + public const int INFINITE = -1; + + [LibraryImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool GetQueuedCompletionStatus( + SafeIoCompletionPort CompletionPort, + out int lpNumberOfBytesTransferred, + out nint lpCompletionKey, + out nint lpOverlapped, + int dwMilliseconds); + + internal static bool GetQueuedCompletionStatus( + SafeIoCompletionPort completionPort, + int timeoutMilliseconds, + out int status) + { + return GetQueuedCompletionStatus( + completionPort, + out status, + lpCompletionKey: out _, + lpOverlapped: out _, + timeoutMilliseconds); + } + } +} 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/PostQueuedCompletionStatus.cs b/src/System.Management.Automation/engine/Interop/Windows/PostQueuedCompletionStatus.cs new file mode 100644 index 00000000000..714eedcc3e5 --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/PostQueuedCompletionStatus.cs @@ -0,0 +1,27 @@ +// 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("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool PostQueuedCompletionStatus( + SafeIoCompletionPort CompletionPort, + int lpNumberOfBytesTransferred, + nint lpCompletionKey, + nint lpOverlapped); + + internal static bool PostQueuedCompletionStatus( + SafeIoCompletionPort completionPort, + int status) + { + return PostQueuedCompletionStatus(completionPort, status, nint.Zero, nint.Zero); + } + } +} 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/SetInformationJobObject.cs b/src/System.Management.Automation/engine/Interop/Windows/SetInformationJobObject.cs new file mode 100644 index 00000000000..37d0e74f1f8 --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/SetInformationJobObject.cs @@ -0,0 +1,46 @@ +// 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 + { + internal const int JobObjectAssociateCompletionPortInformation = 7; + internal const int JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO = 4; + + [StructLayout(LayoutKind.Sequential)] + internal struct JOBOBJECT_ASSOCIATE_COMPLETION_PORT + { + public nint CompletionKey; + public nint CompletionPort; + } + + [LibraryImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static partial bool SetInformationJobObject( + SafeJobHandle hJob, + int JobObjectInformationClass, + ref JOBOBJECT_ASSOCIATE_COMPLETION_PORT lpJobObjectInformation, + int cbJobObjectInformationLength); + + internal static bool SetInformationJobObject( + SafeJobHandle jobHandle, + SafeIoCompletionPort completionPort) + { + JOBOBJECT_ASSOCIATE_COMPLETION_PORT objectInfo = new() + { + CompletionKey = jobHandle.DangerousGetHandle(), + CompletionPort = completionPort.DangerousGetHandle(), + }; + return SetInformationJobObject( + jobHandle, + JobObjectAssociateCompletionPortInformation, + ref objectInfo, + Marshal.SizeOf<JOBOBJECT_ASSOCIATE_COMPLETION_PORT>()); + } + } +} diff --git a/src/System.Management.Automation/engine/Interop/Windows/WNetGetConnection.cs b/src/System.Management.Automation/engine/Interop/Windows/WNetGetConnection.cs index fcdaeb65d3c..01667fb4274 100644 --- a/src/System.Management.Automation/engine/Interop/Windows/WNetGetConnection.cs +++ b/src/System.Management.Automation/engine/Interop/Windows/WNetGetConnection.cs @@ -6,6 +6,7 @@ using System; using System.Buffers; using System.Runtime.InteropServices; +using System.Management.Automation.Internal; internal static partial class Interop { @@ -14,7 +15,7 @@ internal static unsafe partial class Windows private static bool s_WNetApiNotAvailable; [LibraryImport("mpr.dll", EntryPoint = "WNetGetConnectionW", StringMarshalling = StringMarshalling.Utf16)] - internal static partial int WNetGetConnection(ReadOnlySpan<char> localName, Span<char> remoteName, ref uint remoteNameLength); + internal static partial int WNetGetConnection(ReadOnlySpan<char> localName, Span<char> remoteName, ref int remoteNameLength); internal static int GetUNCForNetworkDrive(char drive, out string? uncPath) { @@ -24,46 +25,43 @@ internal static int GetUNCForNetworkDrive(char drive, out string? uncPath) return ERROR_NOT_SUPPORTED; } - uint bufferSize = MAX_PATH; - -#if DEBUG - // In Debug mode buffer size is initially set to 3 and if additional buffer is required, the - // required buffer size is allocated and the WNetGetConnection API is executed with the newly - // allocated buffer size. - bufferSize = 3; -#endif - - ReadOnlySpan<char> driveName = stackalloc char[] { drive, ':', '\0' }; - Span<char> uncBuffer = stackalloc char[(int)bufferSize]; - int errorCode = ERROR_NO_NETWORK; - - try + ReadOnlySpan<char> driveName = [drive, ':', '\0']; + int bufferSize = MAX_PATH; + Span<char> uncBuffer = stackalloc char[MAX_PATH]; + if (InternalTestHooks.WNetGetConnectionBufferSize > 0 && InternalTestHooks.WNetGetConnectionBufferSize <= MAX_PATH) { - errorCode = WNetGetConnection(driveName, uncBuffer, ref bufferSize); - } - catch (System.DllNotFoundException) - { - s_WNetApiNotAvailable = true; - return ERROR_NOT_SUPPORTED; + bufferSize = InternalTestHooks.WNetGetConnectionBufferSize; + uncBuffer = uncBuffer.Slice(0, bufferSize); } - if (errorCode == ERROR_SUCCESS) - { - // exclude null terminator - uncPath = uncBuffer.Slice(0, (int)bufferSize - 1).ToString(); - } - else if (errorCode == ERROR_MORE_DATA) + char[]? rentedArray = null; + while (true) { - char[]? rentedArray = null; + int errorCode; try { - uncBuffer = rentedArray = ArrayPool<char>.Shared.Rent((int)bufferSize); - errorCode = WNetGetConnection(driveName, uncBuffer, ref bufferSize); + try + { + errorCode = WNetGetConnection(driveName, uncBuffer, ref bufferSize); + } + catch (DllNotFoundException) + { + s_WNetApiNotAvailable = true; + return ERROR_NOT_SUPPORTED; + } if (errorCode == ERROR_SUCCESS) { - // exclude null terminator - uncPath = uncBuffer.Slice(0, (int)bufferSize - 1).ToString(); + // Cannot rely on bufferSize as it's only set if + // the first call ended with ERROR_MORE_DATA, + // instead slice at the null terminator. + unsafe + { + fixed (char* uncBufferPtr = uncBuffer) + { + uncPath = new string(uncBufferPtr); + } + } } } finally @@ -73,9 +71,16 @@ internal static int GetUNCForNetworkDrive(char drive, out string? uncPath) ArrayPool<char>.Shared.Return(rentedArray); } } - } - return errorCode; + if (errorCode == ERROR_MORE_DATA) + { + uncBuffer = rentedArray = ArrayPool<char>.Shared.Rent(bufferSize); + } + else + { + return errorCode; + } + } } } } diff --git a/src/System.Management.Automation/engine/LanguagePrimitives.cs b/src/System.Management.Automation/engine/LanguagePrimitives.cs index 87a6dcb7804..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]); } @@ -2077,6 +2076,14 @@ internal static string EnumValues(Type enumType) return string.Join(CultureInfo.CurrentUICulture.TextInfo.ListSeparator, enumHashEntry.names); } + /// <summary> + /// Returns all names for the provided enum type. + /// </summary> + /// <param name="enumType">The enum type to retrieve names from.</param> + /// <returns>Array of enum names for the specified type.</returns> + internal static string[] GetEnumNames(Type enumType) + => EnumSingleTypeConverter.GetEnumHashEntry(enumType).names; + /// <summary> /// Returns all values for the provided enum type. /// </summary> @@ -2093,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, @@ -2935,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 a701b0745c8..8a5f29e2b16 100644 --- a/src/System.Management.Automation/engine/Modules/AnalysisCache.cs +++ b/src/System.Management.Automation/engine/Modules/AnalysisCache.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; @@ -35,11 +36,10 @@ internal static class AnalysisCache private static readonly ConcurrentDictionary<string, string> s_modulesBeingAnalyzed = new(concurrencyLevel: 1, capacity: 2, StringComparer.OrdinalIgnoreCase); - internal static readonly char[] InvalidCommandNameCharacters = new[] - { - '#', ',', '(', ')', '{', '}', '[', ']', '&', '/', '\\', '$', '^', ';', ':', - '"', '\'', '<', '>', '|', '?', '@', '`', '*', '%', '+', '=', '~' - }; + internal static readonly SearchValues<char> InvalidCommandNameCharacters = SearchValues.Create("#,(){}[]&/\\$^;:\"'<>|?@`*%+=~"); + + internal static bool ContainsInvalidCommandNameCharacters(ReadOnlySpan<char> text) + => text.ContainsAny(InvalidCommandNameCharacters); internal static ConcurrentDictionary<string, CommandTypes> GetExportedCommands(string modulePath, bool testOnly, ExecutionContext context) { @@ -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) @@ -345,7 +345,7 @@ private static ConcurrentDictionary<string, CommandTypes> AnalyzeScriptModule(st { if (SessionStateUtilities.MatchesAnyWildcardPattern(command, scriptAnalysisPatterns, true)) { - if (command.IndexOfAny(InvalidCommandNameCharacters) < 0) + if (!ContainsInvalidCommandNameCharacters(command)) { result[command] = CommandTypes.Function; } @@ -357,7 +357,7 @@ private static ConcurrentDictionary<string, CommandTypes> AnalyzeScriptModule(st { var commandName = pair.Key; // These are already filtered - if (commandName.IndexOfAny(InvalidCommandNameCharacters) < 0) + if (!ContainsInvalidCommandNameCharacters(commandName)) { result.AddOrUpdate(commandName, CommandTypes.Alias, static (_, existingCommandType) => existingCommandType | CommandTypes.Alias); @@ -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 743b64a1b0e..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; } @@ -586,24 +574,25 @@ private static IEnumerable<ModuleSpecification> GetCandidateModuleSpecs( } /// <summary> - /// PSEditionArgumentCompleter for PowerShell Edition names. + /// Provides argument completion for PSEdition parameter. /// </summary> public class PSEditionArgumentCompleter : IArgumentCompleter { /// <summary> - /// CompleteArgument. + /// Returns completion results for PSEdition parameter. /// </summary> - public IEnumerable<CompletionResult> CompleteArgument(string commandName, string parameterName, string wordToComplete, CommandAst commandAst, IDictionary fakeBoundParameters) - { - var wordToCompletePattern = WildcardPattern.Get(string.IsNullOrWhiteSpace(wordToComplete) ? "*" : wordToComplete + "*", WildcardOptions.IgnoreCase); - - foreach (var edition in Utils.AllowedEditionValues) - { - if (wordToCompletePattern.IsMatch(edition)) - { - yield return new CompletionResult(edition, edition, CompletionResultType.Text, edition); - } - } - } + /// <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: 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 00b02368270..0a1d0bfc04f 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleCmdletBase.cs @@ -6168,7 +6168,7 @@ private void CheckForDisallowedDotSourcing( } } } - + private static void RemoveNestedModuleFunctions( ExecutionContext context, PSModuleInfo module, @@ -6405,7 +6405,7 @@ private PSModuleInfo AnalyzeScriptFile(string filename, bool force, ExecutionCon // If this has an extension, and it's a relative path, // then we need to ensure it's a fully-qualified path - if ((moduleToProcess.IndexOfAny(Path.GetInvalidPathChars()) == -1) && + if ((!PathUtils.ContainsInvalidPathChars(moduleToProcess)) && Path.HasExtension(moduleToProcess) && (!Path.IsPathRooted(moduleToProcess))) { @@ -7276,7 +7276,7 @@ private static void ImportFunctions(FunctionInfo func, SessionStateInternal targ targetSessionState.ExecutionContext); // Note that the module 'func' and the function table 'functionInfo' instances are now linked - // together (see 'CopiedCommand' in CommandInfo class), so setting visibility on one also + // together (see 'CopiedCommand' in CommandInfo class), so setting visibility on one also // sets it on the other. SetCommandVisibility(isImportModulePrivate, functionInfo); functionInfo.Module = sourceModule; diff --git a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs index b687e502763..538c4775f0a 100644 --- a/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs +++ b/src/System.Management.Automation/engine/Modules/ModuleIntrinsics.cs @@ -10,9 +10,7 @@ using System.Management.Automation.Language; using System.Text; using System.Threading; - using Microsoft.PowerShell.Commands; -using Microsoft.Win32; using Dbg = System.Management.Automation.Diagnostics; @@ -39,18 +37,22 @@ public class ModuleIntrinsics private static readonly string s_windowsPowerShellPSHomeModulePath = Path.Combine(System.Environment.SystemDirectory, "WindowsPowerShell", "v1.0", "Modules"); + static ModuleIntrinsics() + { + // Initialize the module path. + SetModulePath(); + } + internal ModuleIntrinsics(ExecutionContext context) { _context = context; - - // And initialize the module path... - SetModulePath(); + ModuleTable = new Dictionary<string, PSModuleInfo>(StringComparer.OrdinalIgnoreCase); } private readonly ExecutionContext _context; // Holds the module collection... - internal Dictionary<string, PSModuleInfo> ModuleTable { get; } = new Dictionary<string, PSModuleInfo>(StringComparer.OrdinalIgnoreCase); + internal Dictionary<string, PSModuleInfo> ModuleTable { get; } private const int MaxModuleNestingDepth = 10; @@ -176,11 +178,9 @@ private PSModuleInfo CreateModuleImplementation(string name, string path, object sb.SessionState = ss; } - else + else if (moduleCode is string sbText) { - var sbText = moduleCode as string; - if (sbText != null) - sb = ScriptBlock.Create(_context, sbText); + sb = ScriptBlock.Create(_context, sbText); } } @@ -967,7 +967,9 @@ internal static string GetPersonalModulePath() #if UNIX return Platform.SelectProductNameForDirectory(Platform.XDG_Type.USER_MODULES); #else - string myDocumentsPath = InternalTestHooks.SetMyDocumentsSpecialFolderToBlank ? string.Empty : Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); + string myDocumentsPath = InternalTestHooks.SetMyDocumentsSpecialFolderToBlank + ? string.Empty + : Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments, Environment.SpecialFolderOption.DoNotVerify); return string.IsNullOrEmpty(myDocumentsPath) ? null : Path.Combine(myDocumentsPath, Utils.ModuleDirectory); #endif } @@ -1082,91 +1084,80 @@ internal static string GetExpandedEnvironmentVariable(string name, EnvironmentVa return result; } - /// <summary> - /// Checks if a particular string (path) is a member of 'combined path' string (like %Path% or %PSModulePath%) - /// </summary> - /// <param name="pathToScan">'Combined path' string to analyze; can not be null.</param> - /// <param name="pathToLookFor">Path to search for; can not be another 'combined path' (semicolon-separated); can not be null.</param> - /// <returns>Index of pathToLookFor in pathToScan; -1 if not found.</returns> - private static int PathContainsSubstring(string pathToScan, string pathToLookFor) - { - // we don't support if any of the args are null - parent function should ensure this; empty values are ok - Diagnostics.Assert(pathToScan != null, "pathToScan should not be null according to contract of the function"); - Diagnostics.Assert(pathToLookFor != null, "pathToLookFor should not be null according to contract of the function"); - - int pos = 0; // position of the current substring in pathToScan - string[] substrings = pathToScan.Split(Path.PathSeparator, StringSplitOptions.None); // we want to process empty entries - string goodPathToLookFor = pathToLookFor.Trim().TrimEnd(Path.DirectorySeparatorChar); // trailing backslashes and white-spaces will mess up equality comparison - foreach (string substring in substrings) - { - string goodSubstring = substring.Trim().TrimEnd(Path.DirectorySeparatorChar); // trailing backslashes and white-spaces will mess up equality comparison - - // We have to use equality comparison on individual substrings (as opposed to simple 'string.IndexOf' or 'string.Contains') - // because of cases like { pathToScan="C:\Temp\MyDir\MyModuleDir", pathToLookFor="C:\Temp" } - - if (string.Equals(goodSubstring, goodPathToLookFor, StringComparison.OrdinalIgnoreCase)) - { - return pos; // match found - return index of it in the 'pathToScan' string - } - else - { - pos += substring.Length + 1; // '1' is for trailing semicolon - } - } - // if we are here, that means a match was not found - return -1; - } - /// <summary> /// Adds paths to a 'combined path' string (like %Path% or %PSModulePath%) if they are not already there. /// </summary> /// <param name="basePath">Path string (like %Path% or %PSModulePath%).</param> - /// <param name="pathToAdd">Collection of individual paths to add.</param> + /// <param name="pathToAdd">An individual path to add, or multiple paths separated by the path separator character.</param> /// <param name="insertPosition">-1 to append to the end; 0 to insert in the beginning of the string; etc...</param> /// <returns>Result string.</returns> - private static string AddToPath(string basePath, string pathToAdd, int insertPosition) + private static string UpdatePath(string basePath, string pathToAdd, ref int insertPosition) { // we don't support if any of the args are null - parent function should ensure this; empty values are ok - Diagnostics.Assert(basePath != null, "basePath should not be null according to contract of the function"); - Diagnostics.Assert(pathToAdd != null, "pathToAdd should not be null according to contract of the function"); + Dbg.Assert(basePath != null, "basePath should not be null according to contract of the function"); + Dbg.Assert(pathToAdd != null, "pathToAdd should not be null according to contract of the function"); + + // The 'pathToAdd' could be a 'combined path' (path-separator-separated). + string[] newPaths = pathToAdd.Split( + Path.PathSeparator, + StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - StringBuilder result = new StringBuilder(basePath); + if (newPaths.Length is 0) + { + // The 'pathToAdd' doesn't really contain any paths to add. + return basePath; + } + + var result = new StringBuilder(basePath, capacity: basePath.Length + pathToAdd.Length + newPaths.Length); + var addedPaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase); + string[] initialPaths = basePath.Split( + Path.PathSeparator, + StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + + foreach (string p in initialPaths) + { + // Remove the trailing directory separators. + // Trailing white spaces were already removed by 'StringSplitOptions.TrimEntries'. + addedPaths.Add(Path.TrimEndingDirectorySeparator(p)); + } - if (!string.IsNullOrEmpty(pathToAdd)) // we don't want to append empty paths + foreach (string subPathToAdd in newPaths) { - foreach (string subPathToAdd in pathToAdd.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries)) // in case pathToAdd is a 'combined path' (semicolon-separated) + // Remove the trailing directory separators. + // Trailing white spaces were already removed by 'StringSplitOptions.TrimEntries'. + string normalizedPath = Path.TrimEndingDirectorySeparator(subPathToAdd); + if (addedPaths.Contains(normalizedPath)) { - int position = PathContainsSubstring(result.ToString(), subPathToAdd); // searching in effective 'result' value ensures that possible duplicates in pathsToAdd are handled correctly - if (position == -1) // subPathToAdd not found - add it - { - if (insertPosition == -1 || insertPosition > basePath.Length) // append subPathToAdd to the end - { - bool endsWithPathSeparator = false; - if (result.Length > 0) - { - endsWithPathSeparator = (result[result.Length - 1] == Path.PathSeparator); - } + // The normalized sub path was already added - skip it. + continue; + } - if (endsWithPathSeparator) - { - result.Append(subPathToAdd); - } - else - { - result.Append(Path.PathSeparator + subPathToAdd); - } - } - else if (insertPosition > result.Length) - { - // handle case where path is a singleton with no path separator already - result.Append(Path.PathSeparator).Append(subPathToAdd); - } - else // insert at the requested location (this is used by DSC (<Program Files> location) and by 'user-specific location' (SpecialFolder.MyDocuments or EVT.User)) - { - result.Insert(insertPosition, subPathToAdd + Path.PathSeparator); - } + // The normalized sub path was not found - add it. + if (insertPosition is -1 || insertPosition >= result.Length) + { + // Append the normalized sub path to the end. + if (result.Length > 0 && result[^1] != Path.PathSeparator) + { + result.Append(Path.PathSeparator); } + + result.Append(normalizedPath); + // Next insertion should happen at the end. + insertPosition = result.Length; + } + else + { + // Insert at the requested location. + // This is used by the user-specific module path, the shared module path (<Program Files> location), and the PSHome module path. + string strToInsert = normalizedPath + Path.PathSeparator; + result.Insert(insertPosition, strToInsert); + + // Next insertion should happen after the just inserted string. + insertPosition += strToInsert.Length; } + + // Add it to the set. + addedPaths.Add(normalizedPath); } return result.ToString(); @@ -1218,7 +1209,7 @@ public static string GetModulePath(string currentProcessModulePath, string hklmM string psHomeModulePath = GetPSHomeModulePath(); // $PSHome\Modules location // If the variable isn't set, then set it to the default value - if (currentProcessModulePath == null) // EVT.Process does Not exist - really corner case + if (string.IsNullOrEmpty(currentProcessModulePath)) // EVT.Process does Not exist - really corner case { // Handle the default case... if (string.IsNullOrEmpty(hkcuUserModulePath)) // EVT.User does Not exist -> set to <SpecialFolder.MyDocuments> location @@ -1270,21 +1261,6 @@ public static string GetModulePath(string currentProcessModulePath, string hklmM return currentProcessModulePath; } - private static string UpdatePath(string path, string pathToAdd, ref int insertIndex) - { - if (!string.IsNullOrEmpty(pathToAdd)) - { - path = AddToPath(path, pathToAdd, insertIndex); - insertIndex = path.IndexOf(Path.PathSeparator, PathContainsSubstring(path, pathToAdd)); - if (insertIndex != -1) - { - // advance past the path separator - insertIndex++; - } - } - return path; - } - /// <summary> /// Checks if $env:PSModulePath is not set and sets it as appropriate. Note - because these /// strings go through the provider, we need to escape any wildcards before passing them @@ -1358,11 +1334,16 @@ private static string SetModulePath() { string currentModulePath = GetExpandedEnvironmentVariable(Constants.PSModulePathEnvVar, EnvironmentVariableTarget.Process); #if !UNIX - // if the current process and user env vars are the same, it means we need to append the machine one as it's incomplete - // otherwise, the user modified it and we should use the process one + // if the current process and user env vars are the same, it means we need to append the machine one as it's incomplete. + // Otherwise, the user modified it and we should use the process one. if (string.CompareOrdinal(GetExpandedEnvironmentVariable(Constants.PSModulePathEnvVar, EnvironmentVariableTarget.User), currentModulePath) == 0) { - currentModulePath = currentModulePath + Path.PathSeparator + GetExpandedEnvironmentVariable(Constants.PSModulePathEnvVar, EnvironmentVariableTarget.Machine); + string machineScopeValue = GetExpandedEnvironmentVariable(Constants.PSModulePathEnvVar, EnvironmentVariableTarget.Machine); + currentModulePath = string.IsNullOrEmpty(currentModulePath) + ? machineScopeValue + : string.IsNullOrEmpty(machineScopeValue) + ? currentModulePath + : string.Concat(currentModulePath, Path.PathSeparator, machineScopeValue); } #endif string allUsersModulePath = PowerShellConfig.Instance.GetModulePath(ConfigScope.AllUsers); diff --git a/src/System.Management.Automation/engine/Modules/ScriptAnalysis.cs b/src/System.Management.Automation/engine/Modules/ScriptAnalysis.cs index 9644829a02a..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; } @@ -277,10 +264,22 @@ public override AstVisitAction VisitAssignmentStatement(AssignmentStatementAst a // - Exporting module members public override AstVisitAction VisitCommand(CommandAst commandAst) { - string commandName = - commandAst.GetCommandName() ?? - GetSafeValueVisitor.GetSafeValue(commandAst.CommandElements[0], null, GetSafeValueVisitor.SafeValueContext.ModuleAnalysis) as string; + string commandName = commandAst.GetCommandName(); + if (commandName is null) + { + // GetCommandName only works if the name is a string constant. GetSafeValueVistor can evaluate some safe dynamic expressions + try + { + commandName = GetSafeValueVisitor.GetSafeValue(commandAst.CommandElements[0], null, GetSafeValueVisitor.SafeValueContext.ModuleAnalysis) as string; + } + catch (ParseException) + { + // The script is invalid so we can't use GetSafeValue to get the name either. + } + } + // We couldn't get the name of the command. Either it's an anonymous scriptblock: & {"Some script"} + // Or it's a dynamic expression we couldn't safely resolve. if (commandName == null) return AstVisitAction.SkipChildren; 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/MshMemberInfo.cs b/src/System.Management.Automation/engine/MshMemberInfo.cs index 369e0f3fcd6..4bde286d165 100644 --- a/src/System.Management.Automation/engine/MshMemberInfo.cs +++ b/src/System.Management.Automation/engine/MshMemberInfo.cs @@ -28,8 +28,8 @@ namespace System.Management.Automation /// <summary> /// Enumerates all possible types of members. /// </summary> - [TypeConverterAttribute(typeof(LanguagePrimitives.EnumMultipleTypeConverter))] - [FlagsAttribute] + [TypeConverter(typeof(LanguagePrimitives.EnumMultipleTypeConverter))] + [Flags] public enum PSMemberTypes { /// <summary> @@ -120,8 +120,8 @@ public enum PSMemberTypes /// <summary> /// Enumerator for all possible views available on a PSObject. /// </summary> - [TypeConverterAttribute(typeof(LanguagePrimitives.EnumMultipleTypeConverter))] - [FlagsAttribute] + [TypeConverter(typeof(LanguagePrimitives.EnumMultipleTypeConverter))] + [Flags] public enum PSMemberViewTypes { /// <summary> @@ -148,7 +148,7 @@ public enum PSMemberViewTypes /// <summary> /// Match options. /// </summary> - [FlagsAttribute] + [Flags] internal enum MshMemberMatchOptions { /// <summary> @@ -2009,7 +2009,7 @@ public override bool Equals(object obj) } public override int GetHashCode() - => HashCode.Combine(MethodTargetType, ParameterTypes, GenericTypeParameters); + => HashCode.Combine(MethodTargetType, ParameterTypes.SequenceGetHashCode(), GenericTypeParameters.SequenceGetHashCode()); public override string ToString() { 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 8dca2c6d7fc..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 @@ -429,28 +429,28 @@ internal static int GetPercentageComplete(DateTime startTime, TimeSpan expectedD #region DO NOT REMOVE OR RENAME THESE FIELDS - it will break remoting compatibility with Windows PowerShell - [DataMemberAttribute] + [DataMember] private readonly int id; - [DataMemberAttribute] + [DataMember] private int parentId = -1; - [DataMemberAttribute] + [DataMember] private string activity; - [DataMemberAttribute] + [DataMember] private string status; - [DataMemberAttribute] + [DataMember] private string currentOperation; - [DataMemberAttribute] + [DataMember] private int percent = -1; - [DataMemberAttribute] + [DataMember] private int secondsRemaining = -1; - [DataMemberAttribute] + [DataMember] private ProgressRecordType type = ProgressRecordType.Processing; #endregion diff --git a/src/System.Management.Automation/engine/SessionState.cs b/src/System.Management.Automation/engine/SessionState.cs index e65f65ff385..a53c211beb2 100644 --- a/src/System.Management.Automation/engine/SessionState.cs +++ b/src/System.Management.Automation/engine/SessionState.cs @@ -27,7 +27,7 @@ internal sealed partial class SessionStateInternal /// An instance of the PSTraceSource class used for trace output /// using "SessionState" as the category. /// </summary> - [Dbg.TraceSourceAttribute( + [Dbg.TraceSource( "SessionState", "SessionState Class")] private static readonly Dbg.PSTraceSource s_tracer = @@ -337,10 +337,9 @@ internal void InitializeFixedVariables() this.GlobalScope.SetVariable(v.Name, v, asValue: false, force: true, this, CommandOrigin.Internal, fastPath: true); // $PID - Process currentProcess = Process.GetCurrentProcess(); v = new PSVariable( SpecialVariables.PID, - currentProcess.Id, + Environment.ProcessId, ScopedItemOptions.Constant | ScopedItemOptions.AllScope, RunspaceInit.PIDDescription); this.GlobalScope.SetVariable(v.Name, v, asValue: false, force: true, this, CommandOrigin.Internal, fastPath: true); 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/Subsystem/FeedbackSubsystem/IFeedbackProvider.cs b/src/System.Management.Automation/engine/Subsystem/FeedbackSubsystem/IFeedbackProvider.cs index 328eb4eee87..0af184f4e99 100644 --- a/src/System.Management.Automation/engine/Subsystem/FeedbackSubsystem/IFeedbackProvider.cs +++ b/src/System.Management.Automation/engine/Subsystem/FeedbackSubsystem/IFeedbackProvider.cs @@ -243,7 +243,7 @@ internal GeneralCommandErrorFeedback() public Guid Id => _guid; - public string Name => "general"; + public string Name => "General Feedback"; public string Description => "The built-in general feedback source for command errors."; 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 c01bbbc598b..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; } } } @@ -1609,6 +1674,9 @@ public static class InternalTestHooks internal static bool OneDriveTestRecurseOn; internal static string OneDriveTestSymlinkName = "link-Beta"; + // Test out smaller connection buffer size when calling WNetGetConnection. + internal static int WNetGetConnectionBufferSize = -1; + /// <summary>This member is used for internal test purposes.</summary> public static void SetTestHook(string property, object value) { diff --git a/src/System.Management.Automation/engine/cmdlet.cs b/src/System.Management.Automation/engine/cmdlet.cs index 742224c21d5..db2233d44a9 100644 --- a/src/System.Management.Automation/engine/cmdlet.cs +++ b/src/System.Management.Automation/engine/cmdlet.cs @@ -10,7 +10,7 @@ using System.Reflection; using System.Resources; using System.Management.Automation.Internal; -using Dbg = System.Management.Automation.Diagnostics; +using System.Threading; namespace System.Management.Automation { @@ -100,6 +100,11 @@ public bool Stopping } } + /// <summary> + /// Gets the CancellationToken that is signaled when the pipeline is stopping. + /// </summary> + public CancellationToken PipelineStopToken => StopToken; + /// <summary> /// The name of the parameter set in effect. /// </summary> @@ -453,6 +458,9 @@ public void WriteVerbose(string text) } } + internal bool IsWriteVerboseEnabled() + => commandRuntime is not MshCommandRuntime mshRuntime || mshRuntime.IsWriteVerboseEnabled(); + /// <summary> /// Display warning information. /// </summary> @@ -490,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> @@ -598,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> @@ -641,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> @@ -748,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 ba69da88dd3..00a090d4b22 100644 --- a/src/System.Management.Automation/engine/hostifaces/History.cs +++ b/src/System.Management.Automation/engine/hostifaces/History.cs @@ -859,7 +859,7 @@ public class GetHistoryCommand : PSCmdlet /// </summary> /// <value></value> [Parameter(Position = 0, ValueFromPipeline = true)] - [ValidateRangeAttribute((long)1, long.MaxValue)] + [ValidateRange((long)1, long.MaxValue)] public long[] Id { get @@ -887,7 +887,7 @@ public long[] Id /// No of History Entries (starting from last) that are to be displayed. /// </summary> [Parameter(Position = 1)] - [ValidateRangeAttribute(0, (int)Int16.MaxValue)] + [ValidateRange(0, (int)Int16.MaxValue)] public int Count { get @@ -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; } @@ -1518,7 +1518,7 @@ public class ClearHistoryCommand : PSCmdlet /// </summary> [Parameter(ParameterSetName = "IDParameter", Position = 0, HelpMessage = "Specifies the ID of a command in the session history.Clear history clears only the specified command")] - [ValidateRangeAttribute((int)1, int.MaxValue)] + [ValidateRange((int)1, int.MaxValue)] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public int[] Id { @@ -1566,7 +1566,7 @@ public string[] CommandLine /// Clears the specified number of history entries /// </summary> [Parameter(Mandatory = false, Position = 1, HelpMessage = "Clears the specified number of history entries")] - [ValidateRangeAttribute((int)1, int.MaxValue)] + [ValidateRange((int)1, int.MaxValue)] public int Count { get diff --git a/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs b/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs index 9e5612189fa..8e5d30be71f 100644 --- a/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs +++ b/src/System.Management.Automation/engine/hostifaces/HostUtilities.cs @@ -1,38 +1,19 @@ // 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; -using System.Management.Automation.Language; using System.Management.Automation.Runspaces; 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> @@ -44,38 +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 string s_getFuzzyMatchedCommands = @" - [System.Diagnostics.DebuggerHidden()] - param([string] $formatString) - - $formatString -f [string]::Join(', ', (Get-Command $lastError.TargetObject -UseFuzzyMatching -FuzzyMinimumDistance 1 | Select-Object -First 5 -Unique -ExpandProperty Name)) - "; - - private static readonly List<Hashtable> s_suggestions = InitializeSuggestions(); - private static bool HostSupportUnicode() { // Reference: https://github.com/zkat/supports-unicode/blob/main/src/lib.rs @@ -95,34 +44,6 @@ private static bool HostSupportUnicode() return ctype.EndsWith("UTF8") || ctype.EndsWith("UTF-8"); } - private static List<Hashtable> InitializeSuggestions() - { - var suggestions = new List<Hashtable>( - new 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[] { CodeGeneration.EscapeSingleQuotedStringContent(SuggestionStrings.Suggestion_CommandExistsInCurrentDirectory) }, - enabled: true) - }); - - suggestions.Add( - NewSuggestion( - id: 4, - category: "General", - matchType: SuggestionMatchType.ErrorId, - rule: "CommandNotFoundException", - suggestion: ScriptBlock.CreateDelayParsedScriptBlock(s_getFuzzyMatchedCommands, isProductCode: true), - suggestionArgs: new object[] { CodeGeneration.EscapeSingleQuotedStringContent(SuggestionStrings.Suggestion_CommandNotFound) }, - enabled: true)); - - return suggestions; - } - #region GetProfileCommands /// <summary> /// Gets a PSObject whose base object is currentUserCurrentHost and with notes for the other 4 parameters. @@ -227,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"; @@ -301,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/MshHost.cs b/src/System.Management.Automation/engine/hostifaces/MshHost.cs index 32e07f48f3e..bfa79e89f13 100644 --- a/src/System.Management.Automation/engine/hostifaces/MshHost.cs +++ b/src/System.Management.Automation/engine/hostifaces/MshHost.cs @@ -296,6 +296,11 @@ public interface IHostSupportsInteractiveSession /// <summary> /// Called by the engine to notify the host that a runspace push has been requested. /// </summary> + /// <param name="runspace"> + /// The runspace to push. This runspace must be a remote runspace and + /// not a locally created runspace. + /// </param> + /// <exception cref="ArgumentException">The specified runspace is not a remote runspace.</exception> /// <seealso cref="System.Management.Automation.Host.IHostSupportsInteractiveSession.PushRunspace"/> [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Runspace")] [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "runspace")] 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 e7457e2c53d..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,13 +1199,11 @@ public void EndClose(IAsyncResult asyncResult) } /// <summary> - /// Dispose the current runspacepool. + /// Release all resources. /// </summary> public void Dispose() { - _internalPool.Dispose(true); - - GC.SuppressFinalize(this); + _internalPool.Dispose(); } /// <summary> diff --git a/src/System.Management.Automation/engine/hostifaces/RunspacePoolInternal.cs b/src/System.Management.Automation/engine/hostifaces/RunspacePoolInternal.cs index 0fc328f0d5a..1b20fc4c85f 100644 --- a/src/System.Management.Automation/engine/hostifaces/RunspacePoolInternal.cs +++ b/src/System.Management.Automation/engine/hostifaces/RunspacePoolInternal.cs @@ -16,7 +16,7 @@ namespace System.Management.Automation.Runspaces.Internal /// <summary> /// Class which supports pooling local powerShell runspaces. /// </summary> - internal class RunspacePoolInternal + internal class RunspacePoolInternal : IDisposable { #region Private data @@ -813,13 +813,23 @@ public void ReleaseRunspace(Runspace runspace) } } + /// <summary> + /// Release all resources. + /// </summary> + public void Dispose() + { + Dispose(true); + + GC.SuppressFinalize(this); + } + /// <summary> /// Dispose off the current runspace pool. /// </summary> /// <param name="disposing"> /// true to release all the internal resources. /// </param> - public virtual void Dispose(bool disposing) + protected virtual void Dispose(bool disposing) { if (!_isDisposed) { 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 8321d01319d..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); @@ -1276,14 +1276,16 @@ public Array End() /// Clean resources for script commands of this steppable pipeline. /// </summary> /// <remarks> - /// The way we handle 'Clean' blocks in a steppable pipeline makes sure that: - /// 1. The 'Clean' blocks get to run if any exception is thrown from 'Begin/Process/End'. - /// 2. The 'Clean' blocks get to run if 'End' finished successfully. + /// <para> + /// The way we handle 'Clean' blocks in a steppable pipeline makes sure that:</para> + /// <para>1. The 'Clean' blocks get to run if any exception is thrown from 'Begin/Process/End'.</para> + /// <para>2. The 'Clean' blocks get to run if 'End' finished successfully.</para> + /// <para> /// However, this is not enough for a steppable pipeline, because the function, where the steppable /// pipeline gets used, may fail (think about a proxy function). And that may lead to the situation /// where "no exception was thrown from the steppable pipeline" but "the steppable pipeline didn't /// run to the end". In that case, 'Clean' won't run unless it's triggered explicitly on the steppable - /// pipeline. This method allows a user to do that from the 'Clean' block of the proxy function. + /// pipeline. This method allows a user to do that from the 'Clean' block of the proxy function.</para> /// </remarks> public void Clean() { @@ -1386,7 +1388,7 @@ internal ScriptBlockToPowerShellNotSupportedException( /// </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 ScriptBlockToPowerShellNotSupportedException(SerializationInfo info, StreamingContext context) { throw new NotSupportedException(); 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/PSType.cs b/src/System.Management.Automation/engine/parser/PSType.cs index f797411997b..06f978a51ce 100644 --- a/src/System.Management.Automation/engine/parser/PSType.cs +++ b/src/System.Management.Automation/engine/parser/PSType.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Management.Automation.Internal; @@ -276,7 +277,7 @@ private sealed class DefineTypeHelper internal readonly TypeBuilder _staticHelpersTypeBuilder; private readonly Dictionary<string, PropertyMemberAst> _definedProperties; private readonly Dictionary<string, List<Tuple<FunctionMemberAst, Type[]>>> _definedMethods; - private HashSet<Tuple<string, Type>> _abstractProperties; + private Dictionary<Tuple<string, Type>, PropertyInfo> _abstractProperties; internal readonly List<(string fieldName, IParameterMetadataProvider bodyAst, bool isStatic)> _fieldsToInitForMemberFunctions; private bool _baseClassHasDefaultCtor; @@ -444,11 +445,11 @@ private Type GetBaseTypes(Parser parser, TypeDefinitionAst typeDefinitionAst, ou return baseClass ?? typeof(object); } - private bool ShouldImplementProperty(string name, Type type) + private bool ShouldImplementProperty(string name, Type type, [NotNullWhen(true)] out PropertyInfo interfaceProperty) { if (_abstractProperties == null) { - _abstractProperties = new HashSet<Tuple<string, Type>>(); + _abstractProperties = new Dictionary<Tuple<string, Type>, PropertyInfo>(); var allInterfaces = new HashSet<Type>(); // TypeBuilder.GetInterfaces() returns only the interfaces that was explicitly passed to its constructor. @@ -467,7 +468,7 @@ private bool ShouldImplementProperty(string name, Type type) { foreach (var property in interfaceType.GetProperties()) { - _abstractProperties.Add(Tuple.Create(property.Name, property.PropertyType)); + _abstractProperties.Add(Tuple.Create(property.Name, property.PropertyType), property); } } @@ -477,13 +478,13 @@ private bool ShouldImplementProperty(string name, Type type) { if (property.GetAccessors().Any(m => m.IsAbstract)) { - _abstractProperties.Add(Tuple.Create(property.Name, property.PropertyType)); + _abstractProperties.Add(Tuple.Create(property.Name, property.PropertyType), property); } } } } - return _abstractProperties.Contains(Tuple.Create(name, type)); + return _abstractProperties.TryGetValue(Tuple.Create(name, type), out interfaceProperty); } public void DefineMembers() @@ -629,9 +630,19 @@ private PropertyBuilder EmitPropertyIl(PropertyMemberAst propertyMemberAst, Type // The property set and property get methods require a special set of attributes. var getSetAttributes = Reflection.MethodAttributes.SpecialName | Reflection.MethodAttributes.HideBySig; getSetAttributes |= propertyMemberAst.IsPublic ? Reflection.MethodAttributes.Public : Reflection.MethodAttributes.Private; - if (ShouldImplementProperty(propertyMemberAst.Name, type)) + MethodInfo implementingGetter = null; + MethodInfo implementingSetter = null; + if (ShouldImplementProperty(propertyMemberAst.Name, type, out PropertyInfo interfaceProperty)) { - getSetAttributes |= Reflection.MethodAttributes.Virtual; + if (propertyMemberAst.IsStatic) + { + implementingGetter = interfaceProperty.GetGetMethod(); + implementingSetter = interfaceProperty.GetSetMethod(); + } + else + { + getSetAttributes |= Reflection.MethodAttributes.Virtual; + } } if (propertyMemberAst.IsStatic) @@ -677,6 +688,11 @@ private PropertyBuilder EmitPropertyIl(PropertyMemberAst propertyMemberAst, Type getIlGen.Emit(OpCodes.Ret); } + if (implementingGetter != null) + { + _typeBuilder.DefineMethodOverride(getMethod, implementingGetter); + } + // Define the "set" accessor method. MethodBuilder setMethod = _typeBuilder.DefineMethod(string.Concat("set_", propertyMemberAst.Name), getSetAttributes, null, new Type[] { type }); ILGenerator setIlGen = setMethod.GetILGenerator(); @@ -710,6 +726,11 @@ private PropertyBuilder EmitPropertyIl(PropertyMemberAst propertyMemberAst, Type setIlGen.Emit(OpCodes.Ret); + if (implementingSetter != null) + { + _typeBuilder.DefineMethodOverride(setMethod, implementingSetter); + } + // Map the two methods created above to our PropertyBuilder to // their corresponding behaviors, "get" and "set" respectively. property.SetGetMethod(getMethod); diff --git a/src/System.Management.Automation/engine/parser/Parser.cs b/src/System.Management.Automation/engine/parser/Parser.cs index 464231c0402..1592d2e7e7d 100644 --- a/src/System.Management.Automation/engine/parser/Parser.cs +++ b/src/System.Management.Automation/engine/parser/Parser.cs @@ -1487,7 +1487,7 @@ private ITypeName GenericTypeNameRule(Token genericTypeName, Token firstToken, b rBracketToken = null; } - var openGenericType = new TypeName(genericTypeName.Extent, genericTypeName.Text); + var openGenericType = new TypeName(genericTypeName.Extent, genericTypeName.Text, genericArguments.Count); var result = new GenericTypeName( ExtentOf(genericTypeName.Extent, ExtentFromFirstOf(rBracketToken, genericArguments.LastOrDefault(), firstToken)), openGenericType, @@ -3004,8 +3004,8 @@ private StatementAst ConfigurationStatementRule(IEnumerable<AttributeAst> custom dropIntoDebugger: true); } - // Configuration is not supported on ARM64 - if (PsUtils.IsRunningOnProcessorArchitectureARM()) + // Configuration is not supported for ARM or ARM64 process architecture. + if (PsUtils.IsRunningOnProcessArchitectureARM()) { ReportError( configurationToken.Extent, @@ -7154,7 +7154,7 @@ private ExpressionAst UnaryExpressionRule(bool endNumberOnTernaryOpChars = false ParserStrings.UnexpectedAttribute, lastAttribute.TypeName.FullName); - return new ErrorExpressionAst(ExtentOf(token, lastAttribute)); + return new ErrorExpressionAst(ExtentOf(token, lastAttribute), attributes); } expr = new AttributedExpressionAst(ExtentOf(lastAttribute, child), lastAttribute, child); 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/TypeInferenceVisitor.cs b/src/System.Management.Automation/engine/parser/TypeInferenceVisitor.cs index 96a72d3ff06..a7744ac6411 100644 --- a/src/System.Management.Automation/engine/parser/TypeInferenceVisitor.cs +++ b/src/System.Management.Automation/engine/parser/TypeInferenceVisitor.cs @@ -150,6 +150,8 @@ public TypeInferenceContext(PowerShell powerShell) public TypeDefinitionAst CurrentTypeDefinitionAst { get; set; } + public HashSet<IParameterMetadataProvider> AnalyzedCommands { get; } = new HashSet<IParameterMetadataProvider>(); + public TypeInferenceRuntimePermissions RuntimePermissions { get; set; } internal PowerShellExecutionHelper Helper { get; } @@ -195,7 +197,25 @@ internal IList<object> GetMembersByInferredType(PSTypeName typename, bool isStat // Look in the type table first. if (!isStatic) { - var consolidatedString = new ConsolidatedString(new[] { typename.Name }); + // The Ciminstance type adapter adds the full typename with and without a namespace to the list of type names. + // So if we see one with a full typename we need to also get the types for the short version. + // For example: "CimInstance#root/standardcimv2/MSFT_NetFirewallRule" and "CimInstance#MSFT_NetFirewallRule" + int namespaceSeparator = typename.Name.LastIndexOf('/'); + ConsolidatedString consolidatedString; + if (namespaceSeparator != -1 + && typename.Name.StartsWith("Microsoft.Management.Infrastructure.CimInstance#", StringComparison.OrdinalIgnoreCase)) + { + consolidatedString = new ConsolidatedString(new[] + { + typename.Name, + string.Concat("Microsoft.Management.Infrastructure.CimInstance#", typename.Name.AsSpan(namespaceSeparator + 1)) + }); + } + else + { + consolidatedString = new ConsolidatedString(new[] { typename.Name }); + } + results.AddRange(ExecutionContext.TypeTable.GetMembers<PSMemberInfo>(consolidatedString)); } @@ -700,14 +720,176 @@ object ICustomAstVisitor.VisitMergingRedirection(MergingRedirectionAst mergingRe object ICustomAstVisitor.VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) { - // TODO: Handle other kinds of expressions on the right side. - if (binaryExpressionAst.Operator == TokenKind.As && binaryExpressionAst.Right is TypeExpressionAst typeExpression) + switch (binaryExpressionAst.Operator) + { + case TokenKind.And: + case TokenKind.Ccontains: + case TokenKind.Cin: + case TokenKind.Cnotcontains: + case TokenKind.Cnotin: + case TokenKind.Icontains: + case TokenKind.Iin: + case TokenKind.Inotcontains: + case TokenKind.Inotin: + case TokenKind.Is: + case TokenKind.IsNot: + case TokenKind.Or: + case TokenKind.Xor: + // Always returns a bool + return BinaryExpressionAst.BoolTypeNameArray; + + case TokenKind.As: + // TODO: Handle other kinds of expressions on the right side. + if (binaryExpressionAst.Right is TypeExpressionAst typeExpression) + { + var type = typeExpression.TypeName.GetReflectionType(); + var psTypeName = type != null ? new PSTypeName(type) : new PSTypeName(typeExpression.TypeName.FullName); + return new[] { psTypeName }; + } + break; + + case TokenKind.Ceq: + case TokenKind.Cge: + case TokenKind.Cgt: + case TokenKind.Cle: + case TokenKind.Clike: + case TokenKind.Clt: + case TokenKind.Cmatch: + case TokenKind.Cne: + case TokenKind.Cnotlike: + case TokenKind.Cnotmatch: + case TokenKind.Ieq: + case TokenKind.Ige: + case TokenKind.Igt: + case TokenKind.Ile: + case TokenKind.Ilike: + case TokenKind.Ilt: + case TokenKind.Imatch: + case TokenKind.Ine: + case TokenKind.Inotlike: + case TokenKind.Inotmatch: + // Returns a bool or filtered output from the left hand side if it's enumerable + var comparisonOutput = new List<PSTypeName>() { new(typeof(bool)) }; + comparisonOutput.AddRange(InferTypes(binaryExpressionAst.Left)); + return comparisonOutput; + + case TokenKind.Creplace: + case TokenKind.Format: + case TokenKind.Ireplace: + case TokenKind.Join: + // Always returns a string + return BinaryExpressionAst.StringTypeNameArray; + + case TokenKind.Csplit: + case TokenKind.Isplit: + // Always returns a string array + return BinaryExpressionAst.StringArrayTypeNameArray; + + case TokenKind.QuestionQuestion: + // Can return left or right hand side + var nullCoalescingOutput = InferTypes(binaryExpressionAst.Left).ToList(); + nullCoalescingOutput.AddRange(InferTypes(binaryExpressionAst.Right)); + return nullCoalescingOutput.Distinct(); + + default: + break; + } + + List<PSTypeName> lhsTypes = InferTypes(binaryExpressionAst.Left).ToList(); + if (lhsTypes.Count == 0) + { + return lhsTypes; + } + + string methodName; + switch (binaryExpressionAst.Operator) + { + case TokenKind.Divide: + methodName = "op_Division"; + break; + + case TokenKind.Minus: + methodName = "op_Subtraction"; + break; + + case TokenKind.Multiply: + methodName = "op_Multiply"; + break; + + case TokenKind.Plus: + methodName = "op_Addition"; + break; + + case TokenKind.Rem: + methodName = "op_Modulus"; + break; + + case TokenKind.Shl: + methodName = "op_LeftShift"; + break; + + case TokenKind.Shr: + methodName = "op_RightShift"; + break; + + default: + return lhsTypes; + } + + List<PSTypeName> rhsTypes = InferTypes(binaryExpressionAst.Right).ToList(); + HashSet<string> addedReturnTypes = new HashSet<string>(); + List<PSTypeName> result = new List<PSTypeName>(); + foreach (PSTypeName lType in lhsTypes) + { + if (lType.Type is null) + { + continue; + } + + foreach (MethodInfo method in lType.Type.GetMethods(BindingFlags.Public | BindingFlags.Static)) + { + if (!method.Name.Equals(methodName, StringComparison.Ordinal)) + { + continue; + } + + if (rhsTypes.Count == 0) + { + if (addedReturnTypes.Add(method.ReturnType.FullName)) + { + result.Add(new PSTypeName(method.ReturnType)); + } + + continue; + } + + ParameterInfo[] methodParams = method.GetParameters(); + if (methodParams.Length != 2) + { + continue; + } + + foreach (PSTypeName rType in rhsTypes) + { + if (rType.Type is not null && rType.Type.IsAssignableTo(methodParams[1].ParameterType)) + { + if (addedReturnTypes.Add(method.ReturnType.FullName)) + { + result.Add(new PSTypeName(method.ReturnType)); + } + + break; + } + } + } + } + + if (result.Count == 0) { - var type = typeExpression.TypeName.GetReflectionType(); - var psTypeName = type != null ? new PSTypeName(type) : new PSTypeName(typeExpression.TypeName.FullName); - return new[] { psTypeName }; + result.AddRange(lhsTypes); } - return InferTypes(binaryExpressionAst.Left); + + return result; } object ICustomAstVisitor.VisitUnaryExpression(UnaryExpressionAst unaryExpressionAst) @@ -832,9 +1014,20 @@ object ICustomAstVisitor.VisitParamBlock(ParamBlockAst paramBlockAst) object ICustomAstVisitor.VisitNamedBlock(NamedBlockAst namedBlockAst) { var inferredTypes = new List<PSTypeName>(); - for (var index = 0; index < namedBlockAst.Statements.Count; index++) + for (int index = 0; index < namedBlockAst.Statements.Count; index++) { - var ast = namedBlockAst.Statements[index]; + StatementAst ast = namedBlockAst.Statements[index]; + if (ast is AssignmentStatementAst + || (ast is PipelineAst pipe && pipe.PipelineElements.Count == 1 && pipe.PipelineElements[0] is CommandExpressionAst cmd + && cmd.Redirections.Count == 0 && cmd.Expression is UnaryExpressionAst unary + && unary.TokenKind is TokenKind.PostfixPlusPlus or TokenKind.PlusPlus or TokenKind.PostfixMinusMinus or TokenKind.MinusMinus)) + { + // Assignments don't output anything to the named block unless they are wrapped in parentheses. + // When they are wrapped in parentheses, they are seen as PipelineAst. + // Increment/decrement operators like $i++ also don't output anything unless there's a redirection, or they are wrapped in parentheses. + continue; + } + inferredTypes.AddRange(InferTypes(ast)); } @@ -903,8 +1096,19 @@ object ICustomAstVisitor.VisitFunctionDefinition(FunctionDefinitionAst functionD object ICustomAstVisitor.VisitStatementBlock(StatementBlockAst statementBlockAst) { var inferredTypes = new List<PSTypeName>(); - foreach (var ast in statementBlockAst.Statements) + foreach (StatementAst ast in statementBlockAst.Statements) { + if (ast is AssignmentStatementAst + || (ast is PipelineAst pipe && pipe.PipelineElements.Count == 1 && pipe.PipelineElements[0] is CommandExpressionAst cmd + && cmd.Redirections.Count == 0 && cmd.Expression is UnaryExpressionAst unary + && unary.TokenKind is TokenKind.PostfixPlusPlus or TokenKind.PlusPlus or TokenKind.PostfixMinusMinus or TokenKind.MinusMinus)) + { + // Assignments don't output anything to the statement block unless they are wrapped in parentheses. + // When they are wrapped in parentheses, they are seen as PipelineAst. + // Increment operators like $i++ also don't output anything unless there's a redirection, or they are wrapped in parentheses. + continue; + } + inferredTypes.AddRange(InferTypes(ast)); } @@ -1037,7 +1241,18 @@ object ICustomAstVisitor.VisitDoUntilStatement(DoUntilStatementAst doUntilStatem object ICustomAstVisitor.VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst) { - return assignmentStatementAst.Left.Accept(this); + ExpressionAst child = assignmentStatementAst.Left; + while (child is AttributedExpressionAst attributeChild) + { + if (attributeChild is ConvertExpressionAst convert) + { + return new List<PSTypeName>() { new(convert.Type.TypeName) }; + } + + child = attributeChild.Child; + } + + return assignmentStatementAst.Right.Accept(this); } object ICustomAstVisitor.VisitPipeline(PipelineAst pipelineAst) @@ -1068,8 +1283,82 @@ object ICustomAstVisitor.VisitFileRedirection(FileRedirectionAst fileRedirection return TypeInferenceContext.EmptyPSTypeNameArray; } - private void InferTypesFrom(CommandAst commandAst, List<PSTypeName> inferredTypes) + private void InferTypesFrom(CommandAst commandAst, List<PSTypeName> inferredTypes, bool forRedirection = false) { + if (commandAst.Redirections.Count > 0) + { + var mergedStreams = new HashSet<RedirectionStream>(); + bool allStreamsMerged = false; + foreach (RedirectionAst streamRedirection in commandAst.Redirections) + { + if (streamRedirection is FileRedirectionAst fileRedirection) + { + if (!forRedirection && fileRedirection.FromStream is RedirectionStream.All or RedirectionStream.Output) + { + // command output is redirected so it returns nothing. + return; + } + } + else if (streamRedirection is MergingRedirectionAst mergeRedirection && mergeRedirection.ToStream == RedirectionStream.Output) + { + if (mergeRedirection.FromStream == RedirectionStream.All) + { + allStreamsMerged = true; + continue; + } + + _ = mergedStreams.Add(mergeRedirection.FromStream); + } + } + + if (allStreamsMerged) + { + inferredTypes.Add(new PSTypeName(typeof(ErrorRecord))); + inferredTypes.Add(new PSTypeName(typeof(WarningRecord))); + inferredTypes.Add(new PSTypeName(typeof(VerboseRecord))); + inferredTypes.Add(new PSTypeName(typeof(DebugRecord))); + inferredTypes.Add(new PSTypeName(typeof(InformationRecord))); + } + else + { + foreach (RedirectionStream value in mergedStreams) + { + switch (value) + { + case RedirectionStream.Error: + inferredTypes.Add(new PSTypeName(typeof(ErrorRecord))); + break; + + case RedirectionStream.Warning: + inferredTypes.Add(new PSTypeName(typeof(WarningRecord))); + break; + + case RedirectionStream.Verbose: + inferredTypes.Add(new PSTypeName(typeof(VerboseRecord))); + break; + + case RedirectionStream.Debug: + inferredTypes.Add(new PSTypeName(typeof(DebugRecord))); + break; + + case RedirectionStream.Information: + inferredTypes.Add(new PSTypeName(typeof(InformationRecord))); + break; + + default: + break; + } + } + } + } + + if (commandAst.CommandElements[0] is ScriptBlockExpressionAst scriptBlock) + { + // An anonymous function like: & {"Do Something"} + inferredTypes.AddRange(InferTypes(scriptBlock.ScriptBlock)); + return; + } + PseudoBindingInfo pseudoBinding = new PseudoParameterBinder() .DoPseudoParameterBinding(commandAst, null, null, PseudoParameterBinder.BindingType.ParameterCompletion); @@ -1152,6 +1441,21 @@ private void InferTypesFrom(CommandAst commandAst, List<PSTypeName> inferredType } } + if ((commandInfo.OutputType.Count == 0 + || (commandInfo.OutputType.Count == 1 + && (commandInfo.OutputType[0].Name.EqualsOrdinalIgnoreCase(typeof(PSObject).FullName) + || commandInfo.OutputType[0].Name.EqualsOrdinalIgnoreCase(typeof(object).FullName)))) + && commandInfo is IScriptCommandInfo scriptCommandInfo + && scriptCommandInfo.ScriptBlock.Ast is IParameterMetadataProvider scriptBlockWithParams + && _context.AnalyzedCommands.Add(scriptBlockWithParams)) + { + // This is a function without an output type defined (or it's too generic to be useful) + // We can analyze the code inside the function to find out what it actually outputs + // The purpose of the hashset is to avoid infinite loops with functions that call themselves. + inferredTypes.AddRange(InferTypes(scriptBlockWithParams.Body)); + return; + } + // The OutputType property ignores the parameter set specified in the OutputTypeAttribute. // With pseudo-binding, we actually know the candidate parameter sets, so we could take // advantage of it here, but I opted for the simpler code because so few cmdlets use @@ -1659,6 +1963,36 @@ private IEnumerable<PSTypeName> InferTypesFrom(MemberExpressionAst memberExpress return res; } + private static IEnumerable<PSTypeName> InferTypeFromRef(InvokeMemberExpressionAst invokeMember, ExpressionAst refArgument) + { + Type expressionClrType = (invokeMember.Expression as TypeExpressionAst)?.TypeName.GetReflectionType(); + string memberName = (invokeMember.Member as StringConstantExpressionAst)?.Value; + int argumentIndex = invokeMember.Arguments.IndexOf(refArgument); + if (expressionClrType is null || string.IsNullOrEmpty(memberName) || argumentIndex == -1) + { + yield break; + } + + foreach (MemberInfo memberInfo in expressionClrType.GetMember(memberName)) + { + if (memberInfo.MemberType == MemberTypes.Method) + { + var methodInfo = memberInfo as MethodInfo; + ParameterInfo[] methodParams = methodInfo.GetParameters(); + if (methodParams.Length < argumentIndex) + { + continue; + } + + ParameterInfo paramCandidate = methodParams[argumentIndex]; + if (paramCandidate.IsOut) + { + yield return new PSTypeName(paramCandidate.ParameterType.GetElementType()); + } + } + } + } + private void GetTypesOfMembers( PSTypeName thisType, string memberName, @@ -1955,7 +2289,7 @@ private void InferTypeFrom(VariableExpressionAst variableExpressionAst, List<PST return; } - Ast parent = variableExpressionAst.Parent; + Ast currentAst = variableExpressionAst.Parent; if (astVariablePath.IsUnqualified && (SpecialVariables.IsUnderbar(astVariablePath.UserPath) || astVariablePath.UserPath.EqualsOrdinalIgnoreCase(SpecialVariables.PSItem))) @@ -1968,65 +2302,65 @@ private void InferTypeFrom(VariableExpressionAst variableExpressionAst, List<PST // The value in a Switch loop is whichever item is in the condition part of the statement. // The value in Catch/Trap statements is always an error record. bool hasSeenScriptBlock = false; - while (parent is not null) + while (currentAst is not null) { - if (parent is CatchClauseAst or TrapStatementAst) + if (currentAst is CatchClauseAst or TrapStatementAst) { break; } - else if (parent is SwitchStatementAst switchStatement + else if (currentAst is SwitchStatementAst switchStatement && switchStatement.Condition.Extent.EndOffset < variableExpressionAst.Extent.StartOffset) { - parent = switchStatement.Condition; + currentAst = switchStatement.Condition; break; } - else if (parent is ErrorStatementAst switchErrorStatement && switchErrorStatement.Kind?.Kind == TokenKind.Switch) + else if (currentAst is ErrorStatementAst switchErrorStatement && switchErrorStatement.Kind?.Kind == TokenKind.Switch) { if (switchErrorStatement.Conditions?.Count > 0) { if (switchErrorStatement.Conditions[0].Extent.EndOffset < variableExpressionAst.Extent.StartOffset) { - parent = switchErrorStatement.Conditions[0]; + currentAst = switchErrorStatement.Conditions[0]; break; } else { // $_ is inside the condition that is being declared, eg: Get-Process | Sort-Object -Property {switch ($_.Proc<Tab> - parent = switchErrorStatement.Parent; + currentAst = switchErrorStatement.Parent; continue; } } break; } - else if (parent is ScriptBlockExpressionAst) + else if (currentAst is ScriptBlockExpressionAst) { hasSeenScriptBlock = true; } else if (hasSeenScriptBlock) { - if (parent is InvokeMemberExpressionAst invokeMember) + if (currentAst is InvokeMemberExpressionAst invokeMember) { - parent = invokeMember.Expression; + currentAst = invokeMember.Expression; break; } - else if (parent is CommandAst cmdAst && cmdAst.Parent is PipelineAst pipeline && pipeline.PipelineElements.Count > 1) + else if (currentAst is CommandAst cmdAst && cmdAst.Parent is PipelineAst pipeline && pipeline.PipelineElements.Count > 1) { // We've found a pipeline with multiple commands, now we need to determine what command came before the command with the scriptblock: // eg Get-Partition in this example: Get-Disk | Get-Partition | Where {$_} var indexOfPreviousCommand = pipeline.PipelineElements.IndexOf(cmdAst) - 1; if (indexOfPreviousCommand >= 0) { - parent = pipeline.PipelineElements[indexOfPreviousCommand]; + currentAst = pipeline.PipelineElements[indexOfPreviousCommand]; break; } } } - parent = parent.Parent; + currentAst = currentAst.Parent; } - if (parent is CatchClauseAst catchBlock) + if (currentAst is CatchClauseAst catchBlock) { if (catchBlock.CatchTypes.Count > 0) { @@ -2046,7 +2380,7 @@ private void InferTypeFrom(VariableExpressionAst variableExpressionAst, List<PST inferredTypes.Add(new PSTypeName(typeof(ErrorRecord))); } } - else if (parent is TrapStatementAst trap) + else if (currentAst is TrapStatementAst trap) { if (trap.TrapType is not null) { @@ -2061,156 +2395,134 @@ private void InferTypeFrom(VariableExpressionAst variableExpressionAst, List<PST inferredTypes.Add(new PSTypeName(typeof(ErrorRecord))); } } - else if (parent is not null) + else if (currentAst is not null) { - inferredTypes.AddRange(GetInferredEnumeratedTypes(InferTypes(parent))); + inferredTypes.AddRange(GetInferredEnumeratedTypes(InferTypes(currentAst))); } return; } - // For certain variables, we always know their type, well at least we can assume we know. - if (astVariablePath.IsUnqualified) + // Process the well known variable $this + if (astVariablePath.IsUnqualified + && astVariablePath.UnqualifiedPath.EqualsOrdinalIgnoreCase(SpecialVariables.This) + && (_context.CurrentTypeDefinitionAst is not null || _context.CurrentThisType is not null)) { - var isThis = astVariablePath.UserPath.EqualsOrdinalIgnoreCase(SpecialVariables.This); - if (!isThis || (_context.CurrentTypeDefinitionAst == null && _context.CurrentThisType == null)) - { - if (SpecialVariables.AllScopeVariables.TryGetValue(astVariablePath.UserPath, out Type knownType)) - { - if (knownType == typeof(object)) - { - if (_context.TryGetRepresentativeTypeNameFromExpressionSafeEval(variableExpressionAst, out var psType)) - { - inferredTypes.Add(psType); - } - } - else - { - inferredTypes.Add(new PSTypeName(knownType)); - } - - return; - } + // $this is special in script properties and in PowerShell classes + PSTypeName typeName = _context.CurrentThisType ?? new PSTypeName(_context.CurrentTypeDefinitionAst); + inferredTypes.Add(typeName); + return; + } - for (int i = 0; i < SpecialVariables.AutomaticVariables.Length; i++) + // Process other well known variables like $true and $pshome + if (SpecialVariables.AllScopeVariables.TryGetValue(astVariablePath.UnqualifiedPath, out Type knownType)) + { + if (knownType == typeof(object)) + { + if (_context.TryGetRepresentativeTypeNameFromExpressionSafeEval(variableExpressionAst, out var psType)) { - if (!astVariablePath.UserPath.EqualsOrdinalIgnoreCase(SpecialVariables.AutomaticVariables[i])) - { - continue; - } - - var type = SpecialVariables.AutomaticVariableTypes[i]; - if (type != typeof(object)) - { - inferredTypes.Add(new PSTypeName(type)); - } - - break; + inferredTypes.Add(psType); } } else { - var typeName = _context.CurrentThisType ?? new PSTypeName(_context.CurrentTypeDefinitionAst); - inferredTypes.Add(typeName); - return; + inferredTypes.Add(new PSTypeName(knownType)); } - } - else - { - inferredTypes.Add(new PSTypeName(_context.CurrentTypeDefinitionAst)); + return; } - if (parent is null) + // Process automatic variables like $MyInvocation and $PSBoundParameters + for (int i = 0; i < SpecialVariables.AutomaticVariables.Length; i++) { + if (!astVariablePath.UnqualifiedPath.EqualsOrdinalIgnoreCase(SpecialVariables.AutomaticVariables[i])) + { + continue; + } + + Type type = SpecialVariables.AutomaticVariableTypes[i]; + if (type != typeof(object)) + { + inferredTypes.Add(new PSTypeName(type)); + } + return; } - // Look for our variable as a parameter or on the lhs of an assignment - hopefully we'll find either - // a type constraint or at least we can use the rhs to infer the type. - while (parent.Parent != null) + // This visitor + loop finds the start of the current scope and traverses top to bottom to find the nearest variable assignment. + // Then repeats the process for each parent scope. + var assignmentVisitor = new VariableAssignmentVisitor() { - parent = parent.Parent; - } - - int startOffset = variableExpressionAst.Extent.StartOffset; - var targetAsts = (List<Ast>)AstSearcher.FindAll( - parent, - ast => + ScopeIsLocal = true, + LocalScopeOnly = variableExpressionAst.VariablePath.IsLocal || variableExpressionAst.VariablePath.IsPrivate, + StopSearchOffset = variableExpressionAst.Extent.StartOffset, + VariableTarget = variableExpressionAst + }; + while (currentAst is not null) + { + if (currentAst is IParameterMetadataProvider) { - if (ast is ParameterAst || ast is AssignmentStatementAst || ast is CommandAst) - { - return variableExpressionAst.AstAssignsToSameVariable(ast) - && ast.Extent.EndOffset < startOffset; - } - - if (ast is ForEachStatementAst) + if (currentAst is ScriptBlockAst && currentAst.Parent is FunctionDefinitionAst) { - return variableExpressionAst.AstAssignsToSameVariable(ast) - && ast.Extent.StartOffset < startOffset; + // If this scriptblock belongs to a function we want to visit that instead so we can get the parameters + // function X ($Param1){} + currentAst = currentAst.Parent; } - return false; - }, - searchNestedScriptBlocks: true); + assignmentVisitor.ScopeDefinitionAst = currentAst; + currentAst.Visit(assignmentVisitor); - foreach (var ast in targetAsts) - { - if (ast is ParameterAst parameterAst) - { - var currentCount = inferredTypes.Count; - inferredTypes.AddRange(InferTypes(parameterAst)); - - if (inferredTypes.Count != currentCount) + if (assignmentVisitor.LocalScopeOnly + || assignmentVisitor.LastConstraint is not null + || ((assignmentVisitor.LastAssignment is not null || assignmentVisitor.LastAssignmentType is not null) + && (currentAst.Parent is not ScriptBlockExpressionAst scriptBlock || !scriptBlock.IsDotsourced()))) { - return; + // We only care about the parent scopes if no assignment has been made in the current scope + // or if it's a dot sourced scriptblock where an earlier defined type constraint could influence the final type + break; } - } - } - - var assignAsts = targetAsts.OfType<AssignmentStatementAst>().ToArray(); - // If any of the assignments lhs use a type constraint, then we use that. - // Otherwise, we use the rhs of the "nearest" assignment - for (int i = assignAsts.Length - 1; i >= 0; i--) - { - if (assignAsts[i].Left is ConvertExpressionAst lhsConvert) - { - inferredTypes.Add(new PSTypeName(lhsConvert.Type.TypeName)); - return; + assignmentVisitor.ScopeIsLocal = false; + assignmentVisitor.StopSearchOffset = currentAst.Extent.StartOffset; } - } - var foreachAst = targetAsts.OfType<ForEachStatementAst>().FirstOrDefault(); - if (foreachAst != null) - { - inferredTypes.AddRange( - GetInferredEnumeratedTypes(InferTypes(foreachAst.Condition))); - return; + currentAst = currentAst.Parent; } - var commandCompletionAst = targetAsts.OfType<CommandAst>().FirstOrDefault(); - if (commandCompletionAst != null) + // The visitor is done finding the last assignment, now we need to infer the type of that assignment. + if (assignmentVisitor.LastConstraint is not null) { - inferredTypes.AddRange(InferTypes(commandCompletionAst)); - return; + inferredTypes.Add(new PSTypeName(assignmentVisitor.LastConstraint)); } - - int smallestDiff = int.MaxValue; - AssignmentStatementAst closestAssignment = null; - foreach (var assignAst in assignAsts) + else if (assignmentVisitor.LastAssignment is not null) { - var endOffset = assignAst.Extent.EndOffset; - if ((startOffset - endOffset) < smallestDiff) + if (assignmentVisitor.EnumerateAssignment) + { + inferredTypes.AddRange(GetInferredEnumeratedTypes(InferTypes(assignmentVisitor.LastAssignment))); + } + else { - smallestDiff = startOffset - endOffset; - closestAssignment = assignAst; + if (assignmentVisitor.LastAssignment is ConvertExpressionAst convertExpression + && convertExpression.IsRef()) + { + if (convertExpression.Parent is InvokeMemberExpressionAst memberInvoke) + { + inferredTypes.AddRange(InferTypeFromRef(memberInvoke, convertExpression)); + } + } + else if (assignmentVisitor.RedirectionAssignment && assignmentVisitor.LastAssignment is CommandAst cmdAst) + { + InferTypesFrom(cmdAst, inferredTypes, forRedirection: true); + } + else + { + inferredTypes.AddRange(InferTypes(assignmentVisitor.LastAssignment)); + } } } - - if (closestAssignment != null) + else if (assignmentVisitor.LastAssignmentType is not null) { - inferredTypes.AddRange(InferTypes(closestAssignment.Right)); + inferredTypes.Add(assignmentVisitor.LastAssignmentType); } if (_context.TryGetRepresentativeTypeNameFromExpressionSafeEval(variableExpressionAst, out var evalTypeName)) @@ -2556,7 +2868,7 @@ object ICustomAstVisitor2.VisitPipelineChain(PipelineChainAst pipelineChainAst) var types = new List<PSTypeName>(); types.AddRange(InferTypes(pipelineChainAst.LhsPipelineChain)); types.AddRange(InferTypes(pipelineChainAst.RhsPipeline)); - return GetArrayType(types); + return types.Distinct(); } private static CommandBaseAst GetPreviousPipelineCommand(CommandAst commandAst) @@ -2565,95 +2877,459 @@ private static CommandBaseAst GetPreviousPipelineCommand(CommandAst commandAst) var i = pipe.PipelineElements.IndexOf(commandAst); return i != 0 ? pipe.PipelineElements[i - 1] : null; } - } - internal static class TypeInferenceExtension - { - public static bool EqualsOrdinalIgnoreCase(this string s, string t) - { - return string.Equals(s, t, StringComparison.OrdinalIgnoreCase); - } + private sealed class VariableAssignmentVisitor : AstVisitor2 + { + /// <summary> + /// If set, we only look for local/private assignments in the scope of the variable we are inferring. + /// </summary> + internal bool LocalScopeOnly; + + /// <summary> + /// The current scope is local to the variable that is being inferred. + /// </summary> + internal bool ScopeIsLocal; + + /// <summary> + /// The variable that we are trying to determine the type of. + /// </summary> + internal VariableExpressionAst VariableTarget; + + /// <summary> + /// The last type constraint applied to the variable. This takes priority when determining the type of the variable. + /// </summary> + internal ITypeName LastConstraint; + + /// <summary> + /// The last ast that assigned a value to the variable. This determines the value of the variable unless a type constraint has been applied. + /// </summary> + internal Ast LastAssignment; + + /// <summary> + /// The inferred type from the most recent assignment. This is only used for stream redirections to variables, or the special OutVariable common parameters. + /// </summary> + internal PSTypeName LastAssignmentType; + + /// <summary> + /// Whether or not the types from the last assignment should be enumerated. + /// For assignments made by the PipelineVariable parameter or the foreach statement. + /// </summary> + internal bool EnumerateAssignment; + + /// <summary> + /// Whether or not the last assignment was via command redirection. + /// </summary> + internal bool RedirectionAssignment; + + /// <summary> + /// The Ast of the scope we are currently analyzing. + /// </summary> + internal Ast ScopeDefinitionAst; + internal int StopSearchOffset; + private int LastAssignmentOffset = -1; + + private void SetLastAssignment(Ast ast, bool enumerate = false, bool redirectionAssignment = false) + { + if (LastAssignmentOffset < ast.Extent.StartOffset && !VariableTarget.Extent.IsWithin(ast.Extent)) + { + // If the variable we are inferring the value of is inside this assignment then the assignment is invalid + // For example: $x = Get-Random; $x = $x.Where{$_.<Tab>} here the value should be inferred based on Get-Random and not $x = $x... + ClearAssignmentData(); + LastAssignment = ast; + EnumerateAssignment = enumerate; + RedirectionAssignment = redirectionAssignment; + LastAssignmentOffset = ast.Extent.StartOffset; + } + } + + private void SetLastAssignmentType(PSTypeName typeName, IScriptExtent assignmentExtent) + { + if (LastAssignmentOffset < assignmentExtent.StartOffset && !VariableTarget.Extent.IsWithin(assignmentExtent)) + { + // If the variable we are inferring the value of is inside this assignment then the assignment is invalid + // For example: $x = 1..10; Get-Random 2>variable:x -InputObject ($x.<Tab>) here the variable should be inferred based on the initial 1..10 assignment + // and not the error redirected variable. + ClearAssignmentData(); + LastAssignmentType = typeName; + LastAssignmentOffset = assignmentExtent.StartOffset; + } + } + + private void ClearAssignmentData() + { + LastAssignment = null; + LastAssignmentType = null; + EnumerateAssignment = false; + RedirectionAssignment = false; + } + + private bool AssignsToTargetVar(VariableExpressionAst foundVar) + { + if (!foundVar.VariablePath.UnqualifiedPath.EqualsOrdinalIgnoreCase(VariableTarget.VariablePath.UnqualifiedPath)) + { + return false; + } - public static IEnumerable<MethodInfo> GetGetterProperty(this Type type, string propertyName) - { - var res = new List<MethodInfo>(); - foreach (var m in type.GetMethods(BindingFlags.Public | BindingFlags.Instance)) + int scopeIndex = foundVar.VariablePath.UserPath.IndexOf(':'); + string scopeName = scopeIndex == -1 ? string.Empty : foundVar.VariablePath.UserPath.Remove(scopeIndex); + return AssignsToTargetScope(scopeName); + } + + private bool AssignsToTargetVar(string userPath) { - var name = m.Name; - // Equals without string allocation - if (name.Length == propertyName.Length + 4 - && name.StartsWith("get_") - && name.IndexOf(propertyName, 4, StringComparison.Ordinal) == 4) + if (string.IsNullOrEmpty(userPath)) { - res.Add(m); + return false; + } + + string scopeName; + string varName; + int scopeIndex = userPath.IndexOf(':'); + if (scopeIndex == -1) + { + scopeName = string.Empty; + varName = userPath; + } + else + { + scopeName = userPath.Remove(scopeIndex); + varName = userPath.Substring(scopeIndex + 1); + } + + if (!varName.EqualsOrdinalIgnoreCase(VariableTarget.VariablePath.UnqualifiedPath)) + { + return false; } + + return AssignsToTargetScope(scopeName); } - return res; - } + private bool AssignsToTargetScope(string scopeName) + => LocalScopeOnly + ? string.IsNullOrEmpty(scopeName) || scopeName.EqualsOrdinalIgnoreCase("Local") || scopeName.EqualsOrdinalIgnoreCase("Private") + : ScopeIsLocal || !(scopeName.EqualsOrdinalIgnoreCase("Local") || scopeName.EqualsOrdinalIgnoreCase("Private")); - public static bool AstAssignsToSameVariable(this VariableExpressionAst variableAst, Ast ast) - { - var parameterAst = ast as ParameterAst; - var variableAstVariablePath = variableAst.VariablePath; - if (parameterAst != null) + public override AstVisitAction DefaultVisit(Ast ast) { - return variableAstVariablePath.IsUnscopedVariable && - parameterAst.Name.VariablePath.UnqualifiedPath.Equals(variableAstVariablePath.UnqualifiedPath, StringComparison.OrdinalIgnoreCase) && - parameterAst.Parent.Parent.Extent.EndOffset > variableAst.Extent.StartOffset; + if (ast.Extent.StartOffset >= StopSearchOffset) + { + // When visiting do while/until statements, the condition will be visited before the statement block + // The condition itself may not be interesting if it's after the cursor, but the statement block could be + // Example: + // do + // { + // $Var = gci + // $Var.<Tab> + // } + // until($false) + return ast is PipelineBaseAst && ast.Parent is DoUntilStatementAst or DoWhileStatementAst + ? AstVisitAction.SkipChildren + : AstVisitAction.StopVisit; + } + + return AstVisitAction.Continue; } - if (ast is ForEachStatementAst foreachAst) + public override AstVisitAction VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst) { - return variableAstVariablePath.IsUnscopedVariable && - foreachAst.Variable.VariablePath.UnqualifiedPath.Equals(variableAstVariablePath.UnqualifiedPath, StringComparison.OrdinalIgnoreCase); + if (assignmentStatementAst.Extent.StartOffset >= StopSearchOffset) + { + return assignmentStatementAst.Parent is DoUntilStatementAst or DoWhileStatementAst + ? AstVisitAction.SkipChildren + : AstVisitAction.StopVisit; + } + + if (assignmentStatementAst.Left is AttributedExpressionAst attributedExpression) + { + var firstConvertExpression = attributedExpression as ConvertExpressionAst; + ExpressionAst child = attributedExpression.Child; + while (child is AttributedExpressionAst attributeChild) + { + if (firstConvertExpression is null && attributeChild is ConvertExpressionAst convertExpression) + { + // Multiple type constraint can be set on a variable like this: [int] [string] $Var1 = 1 + // But it's the left most type constraint that determines the final type. + firstConvertExpression = convertExpression; + } + + child = attributeChild.Child; + } + + if (child is VariableExpressionAst variableExpression && AssignsToTargetVar(variableExpression)) + { + if (firstConvertExpression is not null) + { + LastConstraint = firstConvertExpression.Type.TypeName; + } + else + { + SetLastAssignment(assignmentStatementAst.Right); + } + } + } + else if (assignmentStatementAst.Left is VariableExpressionAst variableExpression && AssignsToTargetVar(variableExpression)) + { + SetLastAssignment(assignmentStatementAst.Right); + } + + return AstVisitAction.Continue; } - if (ast is CommandAst commandAst) + public override AstVisitAction VisitCommand(CommandAst commandAst) { - string[] variableParameters = { "PV", "PipelineVariable", "OV", "OutVariable" }; - StaticBindingResult bindingResult = StaticParameterBinder.BindCommand(commandAst, false, variableParameters); + if (commandAst.Extent.StartOffset >= StopSearchOffset) + { + return AstVisitAction.StopVisit; + } - if (bindingResult != null) + string commandName = commandAst.GetCommandName(); + if (commandName is not null && CompletionCompleters.s_varModificationCommands.Contains(commandName)) { - foreach (string commandVariableParameter in variableParameters) + StaticBindingResult bindingResult = StaticParameterBinder.BindCommand(commandAst, resolve: false, CompletionCompleters.s_varModificationParameters); + if (bindingResult is not null + && bindingResult.BoundParameters.TryGetValue("Name", out ParameterBindingResult variableName) + && variableName.ConstantValue is string nameValue + && AssignsToTargetVar(nameValue) + && bindingResult.BoundParameters.TryGetValue("Value", out ParameterBindingResult variableValue)) { - if (bindingResult.BoundParameters.TryGetValue(commandVariableParameter, out ParameterBindingResult parameterBindingResult)) + SetLastAssignment(variableValue.Value); + return AstVisitAction.Continue; + } + } + + StaticBindingResult bindResult = StaticParameterBinder.BindCommand(commandAst, resolve: false); + if (bindResult is not null) + { + foreach (string parameterName in CompletionCompleters.s_outVarParameters) + { + if (bindResult.BoundParameters.TryGetValue(parameterName, out ParameterBindingResult outVarBind) + && outVarBind.ConstantValue is string varName + && AssignsToTargetVar(varName)) { - if (string.Equals(variableAstVariablePath.UnqualifiedPath, (string)parameterBindingResult.ConstantValue, StringComparison.OrdinalIgnoreCase)) + // The *Variable parameters actually always results in an ArrayList + // But to make type inference of individual elements better, we say it's a generic list. + switch (parameterName) { - return true; + case "ErrorVariable": + case "ev": + SetLastAssignmentType(new PSTypeName(typeof(List<ErrorRecord>)), commandAst.Extent); + break; + + case "WarningVariable": + case "wv": + SetLastAssignmentType(new PSTypeName(typeof(List<WarningRecord>)), commandAst.Extent); + break; + + case "InformationVariable": + case "iv": + SetLastAssignmentType(new PSTypeName(typeof(List<InformationalRecord>)), commandAst.Extent); + break; + + case "OutVariable": + case "ov": + SetLastAssignment(commandAst); + break; + + default: + break; + } + + return AstVisitAction.Continue; + } + } + + if (commandAst.Parent is PipelineAst pipeline && pipeline.Extent.EndOffset > VariableTarget.Extent.StartOffset) + { + foreach (string parameterName in CompletionCompleters.s_pipelineVariableParameters) + { + if (bindResult.BoundParameters.TryGetValue(parameterName, out ParameterBindingResult pipeVarBind) + && pipeVarBind.ConstantValue is string varName + && AssignsToTargetVar(varName)) + { + SetLastAssignment(commandAst, enumerate: true); + return AstVisitAction.Continue; } } } } - return false; + foreach (RedirectionAst redirection in commandAst.Redirections) + { + if (redirection is FileRedirectionAst fileRedirection + && fileRedirection.Location is StringConstantExpressionAst redirectTarget + && redirectTarget.Value.StartsWith("variable:", StringComparison.OrdinalIgnoreCase) + && redirectTarget.Value.Length > "variable:".Length) + { + string varName = redirectTarget.Value.Substring("variable:".Length); + if (!AssignsToTargetVar(varName)) + { + continue; + } + + switch (fileRedirection.FromStream) + { + case RedirectionStream.Error: + SetLastAssignmentType(new PSTypeName(typeof(ErrorRecord)), commandAst.Extent); + break; + + case RedirectionStream.Warning: + SetLastAssignmentType(new PSTypeName(typeof(WarningRecord)), commandAst.Extent); + break; + + case RedirectionStream.Verbose: + SetLastAssignmentType(new PSTypeName(typeof(VerboseRecord)), commandAst.Extent); + break; + + case RedirectionStream.Debug: + SetLastAssignmentType(new PSTypeName(typeof(DebugRecord)), commandAst.Extent); + break; + + case RedirectionStream.Information: + SetLastAssignmentType(new PSTypeName(typeof(InformationRecord)), commandAst.Extent); + break; + + default: + SetLastAssignment(commandAst, redirectionAssignment: true); + break; + } + } + } + + return AstVisitAction.Continue; } - var assignmentAst = (AssignmentStatementAst)ast; - var lhs = assignmentAst.Left; - if (lhs is ConvertExpressionAst convertExpr) + public override AstVisitAction VisitParameter(ParameterAst parameterAst) { - lhs = convertExpr.Child; + if (parameterAst.Extent.StartOffset >= StopSearchOffset) + { + return AstVisitAction.StopVisit; + } + + if (AssignsToTargetVar(parameterAst.Name)) + { + foreach (AttributeBaseAst attribute in parameterAst.Attributes) + { + if (attribute is TypeConstraintAst typeConstraint) + { + LastConstraint = typeConstraint.TypeName; + return AstVisitAction.Continue; + } + } + } + + return AstVisitAction.Continue; } - if (lhs is not VariableExpressionAst varExpr) + public override AstVisitAction VisitForEachStatement(ForEachStatementAst forEachStatementAst) { - return false; + if (forEachStatementAst.Extent.StartOffset >= StopSearchOffset) + { + return AstVisitAction.StopVisit; + } + + if (AssignsToTargetVar(forEachStatementAst.Variable) && forEachStatementAst.Condition.Extent.EndOffset < VariableTarget.Extent.StartOffset) + { + SetLastAssignment(forEachStatementAst.Condition, enumerate: true); + } + + return AstVisitAction.Continue; + } + + public override AstVisitAction VisitConvertExpression(ConvertExpressionAst convertExpressionAst) + { + if (convertExpressionAst.IsRef() + && convertExpressionAst.Child is VariableExpressionAst varAst + && AssignsToTargetVar(varAst)) + { + SetLastAssignment(convertExpressionAst); + } + + return AstVisitAction.Continue; } - var candidateVarPath = varExpr.VariablePath; - if (candidateVarPath.UserPath.Equals(variableAstVariablePath.UserPath, StringComparison.OrdinalIgnoreCase)) + public override AstVisitAction VisitAttribute(AttributeAst attributeAst) { - return true; + // Attributes can't assign values to variables so they aren't interesting. + return AstVisitAction.SkipChildren; } - // The following condition is making an assumption that at script scope, we didn't use $script:, but in the local scope, we did - // If we are searching anything other than script scope, this is wrong. - if (variableAstVariablePath.IsScript && variableAstVariablePath.UnqualifiedPath.Equals(candidateVarPath.UnqualifiedPath, StringComparison.OrdinalIgnoreCase)) + public override AstVisitAction VisitScriptBlockExpression(ScriptBlockExpressionAst scriptBlockExpressionAst) { - return true; + return scriptBlockExpressionAst.IsDotsourced() + ? AstVisitAction.Continue + : AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitDataStatement(DataStatementAst dataStatementAst) + { + if (dataStatementAst.Extent.StartOffset >= StopSearchOffset) + { + return AstVisitAction.StopVisit; + } + + if (AssignsToTargetVar(dataStatementAst.Variable) && dataStatementAst.Extent.EndOffset < VariableTarget.Extent.StartOffset) + { + SetLastAssignment(dataStatementAst.Body); + } + + return AstVisitAction.SkipChildren; + } + + public override AstVisitAction VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) + { + return functionDefinitionAst == ScopeDefinitionAst + ? AstVisitAction.Continue + : AstVisitAction.SkipChildren; + } + } + } + + internal static class TypeInferenceExtension + { + public static bool EqualsOrdinalIgnoreCase(this string s, string t) + { + return string.Equals(s, t, StringComparison.OrdinalIgnoreCase); + } + + public static IEnumerable<MethodInfo> GetGetterProperty(this Type type, string propertyName) + { + var res = new List<MethodInfo>(); + foreach (var m in type.GetMethods(BindingFlags.Public | BindingFlags.Instance)) + { + var name = m.Name; + // Equals without string allocation + if (name.Length == propertyName.Length + 4 + && name.StartsWith("get_") + && name.IndexOf(propertyName, 4, StringComparison.Ordinal) == 4) + { + res.Add(m); + } + } + + return res; + } + + public static bool IsDotsourced(this ScriptBlockExpressionAst scriptBlockExpressionAst) + { + Ast parent = scriptBlockExpressionAst.Parent; + + // This loop checks if the scriptblock is used as a dot sourced command + // or an argument for a command that uses the local scope eg: ForEach-Object -Process {$Var1 = "Hello"}, {Var2 = $true} + while (parent is not null) + { + if (parent is CommandAst cmdAst) + { + string cmdName = cmdAst.GetCommandName(); + return CompletionCompleters.s_localScopeCommandNames.Contains(cmdName) + || (cmdAst.CommandElements[0] is ScriptBlockExpressionAst && cmdAst.InvocationOperator == TokenKind.Dot); + } + + if (parent is not CommandExpressionAst and not PipelineAst and not StatementBlockAst and not ArrayExpressionAst and not ArrayLiteralAst) + { + break; + } + + parent = parent.Parent; } return false; 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 c83f2189f01..ba5c48a2752 100644 --- a/src/System.Management.Automation/engine/parser/ast.cs +++ b/src/System.Management.Automation/engine/parser/ast.cs @@ -7263,7 +7263,7 @@ internal PipelineAst GenerateCommandCallPipelineAst() FunctionName.Extent, new TypeName( FunctionName.Extent, - typeof(System.Management.Automation.Language.DynamicKeyword).FullName)), + typeof(DynamicKeyword).FullName)), new StringConstantExpressionAst( FunctionName.Extent, "GetKeyword", @@ -7591,6 +7591,8 @@ public override Type StaticType } internal static readonly PSTypeName[] BoolTypeNameArray = new PSTypeName[] { new PSTypeName(typeof(bool)) }; + internal static readonly PSTypeName[] StringTypeNameArray = new PSTypeName[] { new PSTypeName(typeof(string)) }; + internal static readonly PSTypeName[] StringArrayTypeNameArray = new PSTypeName[] { new PSTypeName(typeof(string[])) }; #region Visitors @@ -7937,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 { @@ -8422,9 +8424,11 @@ internal interface ISupportsTypeCaching /// </summary> public sealed class TypeName : ITypeName, ISupportsTypeCaching { - internal readonly string _name; - internal Type _type; - internal readonly IScriptExtent _extent; + private readonly string _name; + private readonly IScriptExtent _extent; + private readonly int _genericArgumentCount; + private Type _type; + internal TypeDefinitionAst _typeDefinitionAst; /// <summary> @@ -8483,6 +8487,23 @@ public TypeName(IScriptExtent extent, string name, string assembly) AssemblyName = assembly; } + /// <summary> + /// Construct a typename that represents a generic type definition. + /// </summary> + /// <param name="extent">The extent of the typename.</param> + /// <param name="name">The name of the type.</param> + /// <param name="genericArgumentCount">The number of generic arguments.</param> + internal TypeName(IScriptExtent extent, string name, int genericArgumentCount) + : this(extent, name) + { + ArgumentOutOfRangeException.ThrowIfLessThan(genericArgumentCount, 0); + + if (genericArgumentCount > 0 && !_name.Contains('`')) + { + _genericArgumentCount = genericArgumentCount; + } + } + /// <summary> /// Returns the full name of the type. /// </summary> @@ -8559,8 +8580,25 @@ public Type GetReflectionType() { if (_type == null) { - Exception e; - Type type = _typeDefinitionAst != null ? _typeDefinitionAst.Type : TypeResolver.ResolveTypeName(this, out e); + Type type = _typeDefinitionAst != null ? _typeDefinitionAst.Type : TypeResolver.ResolveTypeName(this, out _); + + if (type is null && _genericArgumentCount > 0) + { + // We try an alternate name only if it failed to resolve with the original name. + // This is because for a generic type like `System.Tuple<type1, type2>`, the original name `System.Tuple` + // can be resolved and hence `genericTypeName.TypeName.GetReflectionType()` in that case has always been + // returning the type `System.Tuple`. If we change to directly use the alternate name for resolution, the + // return value will become 'System.Tuple`1' in that case, and that's a breaking change. + TypeName newTypeName = new( + _extent, + string.Create(CultureInfo.InvariantCulture, $"{_name}`{_genericArgumentCount}")) + { + AssemblyName = AssemblyName + }; + + type = TypeResolver.ResolveTypeName(newTypeName, out _); + } + if (type != null) { try @@ -8595,7 +8633,11 @@ public Type GetReflectionAttributeType() var result = GetReflectionType(); if (result == null || !typeof(Attribute).IsAssignableFrom(result)) { - var attrTypeName = new TypeName(_extent, FullName + "Attribute"); + TypeName attrTypeName = new(_extent, $"{_name}Attribute", _genericArgumentCount) + { + AssemblyName = AssemblyName + }; + result = attrTypeName.GetReflectionType(); if (result != null && !typeof(Attribute).IsAssignableFrom(result)) { @@ -8888,8 +8930,13 @@ internal Type GetGenericType(Type generic) { if (!TypeName.FullName.Contains('`')) { - var newTypeName = new TypeName(Extent, - string.Create(CultureInfo.InvariantCulture, $"{TypeName.FullName}`{GenericArguments.Count}")); + TypeName newTypeName = new( + Extent, + string.Create(CultureInfo.InvariantCulture, $"{TypeName.Name}`{GenericArguments.Count}")) + { + AssemblyName = TypeName.AssemblyName + }; + generic = newTypeName.GetReflectionType(); } } @@ -8915,8 +8962,13 @@ public Type GetReflectionAttributeType() { if (!TypeName.FullName.Contains('`')) { - var newTypeName = new TypeName(Extent, - string.Create(CultureInfo.InvariantCulture, $"{TypeName.FullName}Attribute`{GenericArguments.Count}")); + TypeName newTypeName = new( + Extent, + string.Create(CultureInfo.InvariantCulture, $"{TypeName.Name}Attribute`{GenericArguments.Count}")) + { + AssemblyName = TypeName.AssemblyName + }; + generic = newTypeName.GetReflectionType(); } } diff --git a/src/System.Management.Automation/engine/pipeline.cs b/src/System.Management.Automation/engine/pipeline.cs index 91bf4359189..191f80e1d89 100644 --- a/src/System.Management.Automation/engine/pipeline.cs +++ b/src/System.Management.Automation/engine/pipeline.cs @@ -7,6 +7,7 @@ using System.Management.Automation.Tracing; using System.Reflection; using System.Runtime.ExceptionServices; +using System.Threading; using Microsoft.PowerShell.Telemetry; using Dbg = System.Management.Automation.Diagnostics; @@ -29,6 +30,7 @@ internal class PipelineProcessor : IDisposable { #region private_members + private readonly CancellationTokenSource _pipelineStopTokenSource = new CancellationTokenSource(); private List<CommandProcessorBase> _commands = new List<CommandProcessorBase>(); private List<PipelineProcessor> _redirectionPipes; private PipelineReader<object> _externalInputPipe; @@ -85,6 +87,7 @@ private void Dispose(bool disposing) _externalErrorOutput = null; _executionScope = null; _eventLogBuffer = null; + _pipelineStopTokenSource.Dispose(); #if !CORECLR // Impersonation Not Supported On CSS SecurityContext.Dispose(); SecurityContext = null; @@ -118,6 +121,11 @@ internal bool ExecutionFailed } } + /// <summary> + /// Gets the CancellationToken that is signaled when the pipeline is stopping. + /// </summary> + internal CancellationToken PipelineStopToken => _pipelineStopTokenSource.Token; + internal void LogExecutionInfo(InvocationInfo invocationInfo, string text) { string message = StringUtil.Format(PipelineStrings.PipelineExecutionInformation, GetCommand(invocationInfo), text); @@ -896,6 +904,8 @@ internal void Stop() return; } + _pipelineStopTokenSource.Cancel(); + // Call StopProcessing() for all the commands. foreach (CommandProcessorBase commandProcessor in commands) { diff --git a/src/System.Management.Automation/engine/regex.cs b/src/System.Management.Automation/engine/regex.cs index 4ca21bd1310..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> @@ -238,9 +255,9 @@ internal static string Escape(string pattern, char[] charsNotToEscape) char ch = pattern[i]; // - // if it is a wildcard char, escape it + // if it is a special char, escape it // - if (IsWildcardChar(ch) && !charsNotToEscape.Contains(ch)) + if (SpecialChars.Contains(ch) && !charsNotToEscape.Contains(ch)) { temp[tempIndex++] = escapeChar; } @@ -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/ClientRemotePowerShell.cs b/src/System.Management.Automation/engine/remoting/client/ClientRemotePowerShell.cs index bee284f0704..631e49fc189 100644 --- a/src/System.Management.Automation/engine/remoting/client/ClientRemotePowerShell.cs +++ b/src/System.Management.Automation/engine/remoting/client/ClientRemotePowerShell.cs @@ -19,7 +19,7 @@ internal sealed class ClientRemotePowerShell : IDisposable { #region Tracer - [TraceSourceAttribute("CRPS", "ClientRemotePowerShell")] + [TraceSource("CRPS", "ClientRemotePowerShell")] private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("CRPS", "ClientRemotePowerShellBase"); #endregion Tracer 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 e7a3a41104a..983180bd5ca 100644 --- a/src/System.Management.Automation/engine/remoting/client/RemoteRunspacePoolInternal.cs +++ b/src/System.Management.Automation/engine/remoting/client/RemoteRunspacePoolInternal.cs @@ -23,7 +23,7 @@ namespace System.Management.Automation.Runspaces.Internal /// Class which supports pooling remote powerShell runspaces /// on the client. /// </summary> - internal class RemoteRunspacePoolInternal : RunspacePoolInternal, IDisposable + internal sealed class RemoteRunspacePoolInternal : RunspacePoolInternal { #region Constructor @@ -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(); @@ -1893,21 +1893,11 @@ private void WaitAndRaiseConnectEventsProc(object state) #region IDisposable - /// <summary> - /// Public method for Dispose. - /// </summary> - public void Dispose() - { - Dispose(true); - - GC.SuppressFinalize(this); - } - /// <summary> /// Release all resources. /// </summary> /// <param name="disposing">If true, release all managed resources.</param> - public override void Dispose(bool disposing) + protected override void Dispose(bool disposing) { // dispose the base class before disposing dataStructure handler. base.Dispose(disposing); diff --git a/src/System.Management.Automation/engine/remoting/client/RemotingErrorRecord.cs b/src/System.Management.Automation/engine/remoting/client/RemotingErrorRecord.cs index bb527828a4d..527bdcf9e21 100644 --- a/src/System.Management.Automation/engine/remoting/client/RemotingErrorRecord.cs +++ b/src/System.Management.Automation/engine/remoting/client/RemotingErrorRecord.cs @@ -101,7 +101,7 @@ public OriginInfo OriginInfo get { return _originInfo; } } - [DataMemberAttribute] + [DataMember] private readonly OriginInfo _originInfo; /// <summary> @@ -150,7 +150,7 @@ public OriginInfo OriginInfo get { return _originInfo; } } - [DataMemberAttribute] + [DataMember] private readonly OriginInfo _originInfo; /// <summary> @@ -192,7 +192,7 @@ public OriginInfo OriginInfo get { return _originInfo; } } - [DataMemberAttribute] + [DataMember] private readonly OriginInfo _originInfo; /// <summary> @@ -221,7 +221,7 @@ public OriginInfo OriginInfo get { return _originInfo; } } - [DataMemberAttribute] + [DataMember] private readonly OriginInfo _originInfo; /// <summary> @@ -250,7 +250,7 @@ public OriginInfo OriginInfo get { return _originInfo; } } - [DataMemberAttribute] + [DataMember] private readonly OriginInfo _originInfo; /// <summary> @@ -292,7 +292,7 @@ public string PSComputerName } } - [DataMemberAttribute] + [DataMember] private readonly string _computerName; /// <summary> @@ -307,7 +307,7 @@ public Guid RunspaceID } } - [DataMemberAttribute] + [DataMember] private readonly Guid _runspaceID; /// <summary> @@ -327,7 +327,7 @@ public Guid InstanceID } } - [DataMemberAttribute] + [DataMember] private Guid _instanceId; /// <summary> diff --git a/src/System.Management.Automation/engine/remoting/client/RemotingProtocol2.cs b/src/System.Management.Automation/engine/remoting/client/RemotingProtocol2.cs index 8aa0c046718..441a3f62fed 100644 --- a/src/System.Management.Automation/engine/remoting/client/RemotingProtocol2.cs +++ b/src/System.Management.Automation/engine/remoting/client/RemotingProtocol2.cs @@ -19,7 +19,7 @@ namespace System.Management.Automation.Internal /// Handles all PowerShell data structure handler communication with the /// server side RunspacePool. /// </summary> - internal class ClientRunspacePoolDataStructureHandler : IDisposable + internal sealed class ClientRunspacePoolDataStructureHandler : IDisposable { private bool _reconnecting = false; @@ -823,7 +823,7 @@ private void StartDisconnectAsync(object state) { remoteSession?.DisconnectAsync(); } - catch + catch { // remoteSession may have already been disposed resulting in unexpected exceptions. } @@ -981,7 +981,7 @@ public void Dispose(bool disposing) /// Base class for ClientPowerShellDataStructureHandler to handle all /// references. /// </summary> - internal class ClientPowerShellDataStructureHandler + internal sealed class ClientPowerShellDataStructureHandler { #region Data Structure Handler events @@ -1152,8 +1152,8 @@ internal void SendHostResponseToServer(RemoteHostResponse hostResponse) RemoteDataObject<PSObject> dataToBeSent = RemoteDataObject<PSObject>.CreateFrom(RemotingDestination.Server, RemotingDataType.RemotePowerShellHostResponseData, - clientRunspacePoolId, - clientPowerShellId, + _clientRunspacePoolId, + _clientPowerShellId, hostResponse.Encode()); TransportManager.DataToBeSentCollection.Add<PSObject>(dataToBeSent, @@ -1175,7 +1175,7 @@ internal void SendInput(ObjectStreamBase inputstream) { // send input closed information to server SendDataAsync(RemotingEncoder.GeneratePowerShellInputEnd( - clientRunspacePoolId, clientPowerShellId)); + _clientRunspacePoolId, _clientPowerShellId)); } } else @@ -1204,10 +1204,10 @@ internal void SendInput(ObjectStreamBase inputstream) internal void ProcessReceivedData(RemoteDataObject<PSObject> receivedData) { // verify if this data structure handler is the intended recipient - if (receivedData.PowerShellId != clientPowerShellId) + if (receivedData.PowerShellId != _clientPowerShellId) { throw new PSRemotingDataStructureException(RemotingErrorIdStrings.PipelineIdsDoNotMatch, - receivedData.PowerShellId, clientPowerShellId); + receivedData.PowerShellId, _clientPowerShellId); } // decode the message and take appropriate action @@ -1470,13 +1470,6 @@ internal void ProcessRobustConnectionNotification( #endregion Data Structure Handler Methods - #region Protected Members - - protected Guid clientRunspacePoolId; - protected Guid clientPowerShellId; - - #endregion Protected Members - #region Constructors /// <summary> @@ -1493,8 +1486,8 @@ internal ClientPowerShellDataStructureHandler(BaseClientCommandTransportManager Guid clientRunspacePoolId, Guid clientPowerShellId) { TransportManager = transportManager; - this.clientRunspacePoolId = clientRunspacePoolId; - this.clientPowerShellId = clientPowerShellId; + _clientRunspacePoolId = clientRunspacePoolId; + _clientPowerShellId = clientPowerShellId; transportManager.SignalCompleted += OnSignalCompleted; } @@ -1510,7 +1503,7 @@ internal Guid PowerShellId { get { - return clientPowerShellId; + return _clientPowerShellId; } } @@ -1558,23 +1551,23 @@ 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) { SendDataAsync(RemotingEncoder.GeneratePowerShellInput(inputObject, - clientRunspacePoolId, clientPowerShellId)); + _clientRunspacePoolId, _clientPowerShellId)); } 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) { SendDataAsync(RemotingEncoder.GeneratePowerShellInput(inputObject, - clientRunspacePoolId, clientPowerShellId)); + _clientRunspacePoolId, _clientPowerShellId)); } // we are sending input end to the server. Ignore the future @@ -1583,7 +1576,7 @@ private void WriteInput(ObjectStreamBase inputstream) inputstream.DataReady -= HandleInputDataReady; // stream close: send end of input SendDataAsync(RemotingEncoder.GeneratePowerShellInputEnd( - clientRunspacePoolId, clientPowerShellId)); + _clientRunspacePoolId, _clientPowerShellId)); } } @@ -1605,6 +1598,9 @@ private void SetupTransportManager(bool inDisconnectMode) #region Private Members + private readonly Guid _clientRunspacePoolId; + private readonly Guid _clientPowerShellId; + // object for synchronizing input to be sent // to server powershell private readonly object _inputSyncObject = new object(); @@ -1623,7 +1619,7 @@ private enum connectionStates #endregion Private Members } - internal class InformationalMessage + internal sealed class InformationalMessage { internal object Message { get; } 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 772beb61040..499062d7a18 100644 --- a/src/System.Management.Automation/engine/remoting/client/clientremotesession.cs +++ b/src/System.Management.Automation/engine/remoting/client/clientremotesession.cs @@ -55,7 +55,7 @@ internal class ClientRemoteSessionContext /// </summary> internal abstract class ClientRemoteSession : RemoteSession { - [TraceSourceAttribute("CRSession", "ClientRemoteSession")] + [TraceSource("CRSession", "ClientRemoteSession")] private static readonly PSTraceSource s_trace = PSTraceSource.GetTracer("CRSession", "ClientRemoteSession"); #region Public_Method_API @@ -177,7 +177,7 @@ internal RemoteRunspacePoolInternal GetRunspacePool(Guid clientRunspacePoolId) /// </summary> internal class ClientRemoteSessionImpl : ClientRemoteSession, IDisposable { - [TraceSourceAttribute("CRSessionImpl", "ClientRemoteSessionImpl")] + [TraceSource("CRSessionImpl", "ClientRemoteSessionImpl")] private static readonly PSTraceSource s_trace = PSTraceSource.GetTracer("CRSessionImpl", "ClientRemoteSessionImpl"); private PSRemotingCryptoHelperClient _cryptoHelper = null; @@ -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/clientremotesessionprotocolstatemachine.cs b/src/System.Management.Automation/engine/remoting/client/clientremotesessionprotocolstatemachine.cs index da63b846543..2dea06d8ced 100644 --- a/src/System.Management.Automation/engine/remoting/client/clientremotesessionprotocolstatemachine.cs +++ b/src/System.Management.Automation/engine/remoting/client/clientremotesessionprotocolstatemachine.cs @@ -31,7 +31,7 @@ namespace System.Management.Automation.Remoting /// </summary> internal class ClientRemoteSessionDSHandlerStateMachine { - [TraceSourceAttribute("CRSessionFSM", "CRSessionFSM")] + [TraceSource("CRSessionFSM", "CRSessionFSM")] private static readonly PSTraceSource s_trace = PSTraceSource.GetTracer("CRSessionFSM", "CRSessionFSM"); /// <summary> 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/client/remotingprotocolimplementation.cs b/src/System.Management.Automation/engine/remoting/client/remotingprotocolimplementation.cs index 71720f6cacd..d36ddff8be3 100644 --- a/src/System.Management.Automation/engine/remoting/client/remotingprotocolimplementation.cs +++ b/src/System.Management.Automation/engine/remoting/client/remotingprotocolimplementation.cs @@ -13,9 +13,9 @@ namespace System.Management.Automation.Remoting /// <summary> /// Implements ServerRemoteSessionDataStructureHandler. /// </summary> - internal class ClientRemoteSessionDSHandlerImpl : ClientRemoteSessionDataStructureHandler, IDisposable + internal sealed class ClientRemoteSessionDSHandlerImpl : ClientRemoteSessionDataStructureHandler, IDisposable { - [TraceSourceAttribute("CRSDSHdlerImpl", "ClientRemoteSessionDSHandlerImpl")] + [TraceSource("CRSDSHdlerImpl", "ClientRemoteSessionDSHandlerImpl")] private static readonly PSTraceSource s_trace = PSTraceSource.GetTracer("CRSDSHdlerImpl", "ClientRemoteSessionDSHandlerImpl"); private const string resBaseName = "remotingerroridstrings"; @@ -735,26 +735,12 @@ internal void ProcessNonSessionMessages(RemoteDataObject<PSObject> rcvdData) #region IDisposable - /// <summary> - /// Public method for dispose. - /// </summary> - public void Dispose() - { - Dispose(true); - - GC.SuppressFinalize(this); - } - /// <summary> /// Release all resources. /// </summary> - /// <param name="disposing">If true, release all managed resources.</param> - protected void Dispose(bool disposing) + public void Dispose() { - if (disposing) - { - _transportManager.Dispose(); - } + _transportManager.Dispose(); } #endregion IDisposable 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 626dd5133d1..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")] - [ValidateRangeAttribute(-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 dd5a66ef4bc..22af9adf9ff 100644 --- a/src/System.Management.Automation/engine/remoting/commands/getrunspacecommand.cs +++ b/src/System.Management.Automation/engine/remoting/commands/getrunspacecommand.cs @@ -8,6 +8,7 @@ using System.Management.Automation.Internal; using System.Management.Automation.Remoting; using System.Management.Automation.Runspaces; +using System.Runtime.InteropServices; using Dbg = System.Management.Automation.Diagnostics; @@ -282,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> @@ -341,8 +342,22 @@ public string CertificateThumbprint /// </summary> protected override void BeginProcessing() { - base.BeginProcessing(); +#if UNIX + if (ComputerName?.Length > 0) + { + ErrorRecord err = new( + new NotImplementedException( + PSRemotingErrorInvariants.FormatResourceString( + RemotingErrorIdStrings.UnsupportedOSForRemoteEnumeration, + RuntimeInformation.OSDescription)), + "PSSessionComputerNameUnix", + ErrorCategory.NotImplemented, + null); + ThrowTerminatingError(err); + } +#endif + base.BeginProcessing(); ConfigurationName ??= string.Empty; } 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 7fae8118310..8d53adf0ca8 100644 --- a/src/System.Management.Automation/engine/remoting/common/RemoteSessionHyperVSocket.cs +++ b/src/System.Management.Automation/engine/remoting/common/RemoteSessionHyperVSocket.cs @@ -7,12 +7,14 @@ 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 { - [SerializableAttribute] + [Serializable] internal class HyperVSocketEndPoint : EndPoint { #region Members @@ -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 8517fb059f5..c451ba32f45 100644 --- a/src/System.Management.Automation/engine/remoting/common/fragmentor.cs +++ b/src/System.Management.Automation/engine/remoting/common/fragmentor.cs @@ -432,7 +432,7 @@ internal static int GetBlobLength(byte[] fragmentBytes, int startIndex) /// </summary> internal class SerializedDataStream : Stream, IDisposable { - [TraceSourceAttribute("SerializedDataStream", "SerializedDataStream")] + [TraceSource("SerializedDataStream", "SerializedDataStream")] private static readonly PSTraceSource s_trace = PSTraceSource.GetTracer("SerializedDataStream", "SerializedDataStream"); #region Global Constants @@ -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/common/remotingexceptions.cs b/src/System.Management.Automation/engine/remoting/common/remotingexceptions.cs index 01c3d63c8d9..8830bb32152 100644 --- a/src/System.Management.Automation/engine/remoting/common/remotingexceptions.cs +++ b/src/System.Management.Automation/engine/remoting/common/remotingexceptions.cs @@ -18,6 +18,7 @@ internal enum PSRemotingErrorId : uint // OS related 1-9 DefaultRemotingExceptionMessage = 0, OutOfMemory = 1, + UnsupportedOSForRemoteEnumeration = 2, // Pipeline related range: 10-99 PipelineIdsDoNotMatch = 10, diff --git a/src/System.Management.Automation/engine/remoting/fanin/BaseTransportManager.cs b/src/System.Management.Automation/engine/remoting/fanin/BaseTransportManager.cs index 2a9d68bd55f..c3cf3b278aa 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/BaseTransportManager.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/BaseTransportManager.cs @@ -195,7 +195,7 @@ public abstract class BaseTransportManager : IDisposable { #region tracer - [TraceSourceAttribute("Transport", "Traces BaseWSManTransportManager")] + [TraceSource("Transport", "Traces BaseWSManTransportManager")] private static readonly PSTraceSource s_baseTracer = PSTraceSource.GetTracer("Transport", "Traces BaseWSManTransportManager"); #endregion @@ -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); @@ -472,7 +472,7 @@ namespace System.Management.Automation.Remoting.Client public abstract class BaseClientTransportManager : BaseTransportManager, IDisposable { #region Tracer - [TraceSourceAttribute("ClientTransport", "Traces ClientTransportManager")] + [TraceSource("ClientTransport", "Traces ClientTransportManager")] internal static PSTraceSource tracer = PSTraceSource.GetTracer("ClientTransport", "Traces ClientTransportManager"); #endregion @@ -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 9cea542ab95..07e17e8b63a 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/InitialSessionStateProvider.cs @@ -340,7 +340,7 @@ public abstract class PSSessionConfiguration : IDisposable /// <summary> /// Tracer for Server Remote session. /// </summary> - [TraceSourceAttribute("ServerRemoteSession", "ServerRemoteSession")] + [TraceSource("ServerRemoteSession", "ServerRemoteSession")] private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("ServerRemoteSession", "ServerRemoteSession"); #endregion tracer @@ -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 f13970274b6..a0f38a78a07 100644 --- a/src/System.Management.Automation/engine/remoting/fanin/PriorityCollection.cs +++ b/src/System.Management.Automation/engine/remoting/fanin/PriorityCollection.cs @@ -301,7 +301,7 @@ internal class ReceiveDataCollection : IDisposable { #region tracer - [TraceSourceAttribute("Transport", "Traces BaseWSManTransportManager")] + [TraceSource("Transport", "Traces BaseWSManTransportManager")] private static readonly PSTraceSource s_baseTracer = PSTraceSource.GetTracer("Transport", "Traces BaseWSManTransportManager"); #endregion @@ -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 7f0e5636a4e..b1e05909d2c 100644 --- a/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs +++ b/src/System.Management.Automation/engine/remoting/server/serverremotesession.cs @@ -69,7 +69,7 @@ internal ServerRemoteSessionContext() /// </summary> internal class ServerRemoteSession : RemoteSession { - [TraceSourceAttribute("ServerRemoteSession", "ServerRemoteSession")] + [TraceSource("ServerRemoteSession", "ServerRemoteSession")] private static readonly PSTraceSource s_trace = PSTraceSource.GetTracer("ServerRemoteSession", "ServerRemoteSession"); private readonly PSSenderInfo _senderInfo; @@ -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/remoting/server/serverremotesessionstatemachine.cs b/src/System.Management.Automation/engine/remoting/server/serverremotesessionstatemachine.cs index 3a6bb4c1f53..67d47ea404c 100644 --- a/src/System.Management.Automation/engine/remoting/server/serverremotesessionstatemachine.cs +++ b/src/System.Management.Automation/engine/remoting/server/serverremotesessionstatemachine.cs @@ -31,7 +31,7 @@ namespace System.Management.Automation.Remoting /// </summary> internal class ServerRemoteSessionDSHandlerStateMachine { - [TraceSourceAttribute("ServerRemoteSessionDSHandlerStateMachine", "ServerRemoteSessionDSHandlerStateMachine")] + [TraceSource("ServerRemoteSessionDSHandlerStateMachine", "ServerRemoteSessionDSHandlerStateMachine")] private static readonly PSTraceSource s_trace = PSTraceSource.GetTracer("ServerRemoteSessionDSHandlerStateMachine", "ServerRemoteSessionDSHandlerStateMachine"); private readonly ServerRemoteSession _session; diff --git a/src/System.Management.Automation/engine/runtime/Binding/Binders.cs b/src/System.Management.Automation/engine/runtime/Binding/Binders.cs index de36b9d7249..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,19 +6963,16 @@ internal static DynamicMetaObject InvokeDotNetMethod( expr = Expression.Block(expr, ExpressionCache.AutomationNullConstant); } - // Expression block runs two expressions in order: - // - Log method invocation to AMSI Notifications (can throw PSSecurityException) - // - Invoke method - string targetName = methodInfo.ReflectedType?.FullName ?? string.Empty; - expr = Expression.Block( - Expression.Call( - CachedReflectionInfo.MemberInvocationLoggingOps_LogMemberInvocation, - Expression.Constant(targetName), - Expression.Constant(name), - Expression.NewArrayInit( - typeof(object), - args.Select(static e => e.Expression.Cast(typeof(object))))), - expr); + 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 @@ -7119,6 +7136,7 @@ internal static Expression InvokeMethod(MethodBase mi, DynamicMetaObject target, invocationType != MethodInvocationType.NonVirtual; var parameters = mi.GetParameters(); var argExprs = new Expression[parameters.Length]; + var argsToLog = new List<Expression>(Math.Max(parameters.Length, args.Length)); for (int i = 0; i < parameters.Length; ++i) { @@ -7143,16 +7161,21 @@ internal static Expression InvokeMethod(MethodBase mi, DynamicMetaObject target, if (expandParameters) { - argExprs[i] = Expression.NewArrayInit( - paramElementType, - args.Skip(i).Select( - a => a.CastOrConvertMethodArgument( + IEnumerable<Expression> elements = args + .Skip(i) + .Select(a => + a.CastOrConvertMethodArgument( paramElementType, paramName, mi.Name, allowCastingToByRefLikeType: false, temps, - initTemps))); + initTemps)) + .ToList(); + + argExprs[i] = Expression.NewArrayInit(paramElementType, elements); + // User specified the element arguments, so we log them instead of the compiler-created array. + argsToLog.AddRange(elements); } else { @@ -7163,16 +7186,28 @@ internal static Expression InvokeMethod(MethodBase mi, DynamicMetaObject target, allowCastingToByRefLikeType: false, temps, initTemps); + argExprs[i] = arg; + argsToLog.Add(arg); } } else if (i >= args.Length) { - Diagnostics.Assert(parameters[i].IsOptional, + // We don't log the default value for an optional parameter, as it's not specified by the user. + Diagnostics.Assert( + parameters[i].IsOptional, "if there are too few arguments, FindBestMethod should only succeed if parameters are optional"); + 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) @@ -7207,17 +7242,25 @@ internal static Expression InvokeMethod(MethodBase mi, DynamicMetaObject target, var psRefValue = Expression.Property(args[i].Expression.Cast(typeof(PSReference)), CachedReflectionInfo.PSReference_Value); initTemps.Add(Expression.Assign(temp, psRefValue.Convert(temp.Type))); copyOutTemps.Add(Expression.Assign(psRefValue, temp.Cast(typeof(object)))); + argExprs[i] = temp; + argsToLog.Add(temp); } else { - argExprs[i] = args[i].CastOrConvertMethodArgument( + var convertedArg = args[i].CastOrConvertMethodArgument( parameterType, paramName, mi.Name, allowCastingToByRefLikeType, temps, initTemps); + + argExprs[i] = convertedArg; + // If the converted arg is a byref-like type, then we log the original arg. + argsToLog.Add(convertedArg.Type.IsByRefLike + ? args[i].Expression + : convertedArg); } } } @@ -7263,6 +7306,12 @@ internal static Expression InvokeMethod(MethodBase mi, DynamicMetaObject target, } } + // We need to add one expression to log the .NET invocation before actually invoking: + // - Log method invocation to AMSI Notifications (can throw PSSecurityException) + // - Invoke method + string targetName = mi.ReflectedType?.FullName ?? string.Empty; + string methodName = mi.Name is ".ctor" ? "new" : mi.Name; + if (temps.Count > 0) { if (call.Type != typeof(void) && copyOutTemps.Count > 0) @@ -7273,8 +7322,13 @@ internal static Expression InvokeMethod(MethodBase mi, DynamicMetaObject target, copyOutTemps.Add(retValue); } + AddMemberInvocationLogging(initTemps, targetName, methodName, argsToLog); call = Expression.Block(call.Type, temps, initTemps.Append(call).Concat(copyOutTemps)); } + else + { + call = AddMemberInvocationLogging(call, targetName, methodName, argsToLog); + } return call; } @@ -7474,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) @@ -7490,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; @@ -7566,6 +7620,55 @@ internal static void InvalidateCache() } } +#nullable enable + private static Expression AddMemberInvocationLogging( + Expression expr, + string targetName, + string name, + List<Expression> args) + { +#if UNIX + // For efficiency this is a no-op on non-Windows platforms. + return expr; +#else + Expression[] invocationArgs = new Expression[args.Count]; + for (int i = 0; i < args.Count; i++) + { + invocationArgs[i] = args[i].Cast(typeof(object)); + } + + return Expression.Block( + Expression.Call( + CachedReflectionInfo.MemberInvocationLoggingOps_LogMemberInvocation, + Expression.Constant(targetName), + Expression.Constant(name), + Expression.NewArrayInit(typeof(object), invocationArgs)), + expr); +#endif + } + + private static void AddMemberInvocationLogging( + List<Expression> exprs, + string targetName, + string name, + List<Expression> args) + { +#if !UNIX + Expression[] invocationArgs = new Expression[args.Count]; + for (int i = 0; i < args.Count; i++) + { + invocationArgs[i] = args[i].Cast(typeof(object)); + } + + exprs.Add(Expression.Call( + CachedReflectionInfo.MemberInvocationLoggingOps_LogMemberInvocation, + Expression.Constant(targetName), + Expression.Constant(name), + Expression.NewArrayInit(typeof(object), invocationArgs))); +#endif + } +#nullable disable + #endregion } @@ -7826,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 9e2fe5db58a..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; } @@ -4991,7 +4991,7 @@ private static string DecodeString(string s) #endregion misc - [TraceSourceAttribute("InternalDeserializer", "InternalDeserializer class")] + [TraceSource("InternalDeserializer", "InternalDeserializer class")] private static readonly PSTraceSource s_trace = PSTraceSource.GetTracer("InternalDeserializer", "InternalDeserializer class"); } @@ -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/CabinetNativeApi.cs b/src/System.Management.Automation/help/CabinetNativeApi.cs index 9abe5a894b4..fd369ca03be 100644 --- a/src/System.Management.Automation/help/CabinetNativeApi.cs +++ b/src/System.Management.Automation/help/CabinetNativeApi.cs @@ -543,7 +543,7 @@ internal static FileShare ConvertPermissionModeToFileShare(int pmode) #region IO classes, structures, and enums - [FlagsAttribute] + [Flags] internal enum PermissionMode : int { None = 0x0000, @@ -551,7 +551,7 @@ internal enum PermissionMode : int Read = 0x0100 } - [FlagsAttribute] + [Flags] internal enum OpFlags : int { RdOnly = 0x0000, diff --git a/src/System.Management.Automation/help/HelpCommands.cs b/src/System.Management.Automation/help/HelpCommands.cs index 1b451637a5d..3bed2a26820 100644 --- a/src/System.Management.Automation/help/HelpCommands.cs +++ b/src/System.Management.Automation/help/HelpCommands.cs @@ -734,10 +734,8 @@ internal static void VerifyParameterForbiddenInRemoteRunspace(Cmdlet cmdlet, str #endregion #region trace - - [TraceSourceAttribute("GetHelpCommand ", "GetHelpCommand ")] - private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("GetHelpCommand ", "GetHelpCommand "); - + [TraceSource("GetHelpCommand", "GetHelpCommand")] + private static readonly PSTraceSource s_tracer = PSTraceSource.GetTracer("GetHelpCommand", "GetHelpCommand"); #endregion } 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/SaveHelpCommand.cs b/src/System.Management.Automation/help/SaveHelpCommand.cs index 2cefef9e23f..5df8f974f5c 100644 --- a/src/System.Management.Automation/help/SaveHelpCommand.cs +++ b/src/System.Management.Automation/help/SaveHelpCommand.cs @@ -88,7 +88,7 @@ public string[] LiteralPath [Parameter(Position = 1, ValueFromPipelineByPropertyName = true, ValueFromPipeline = true, ParameterSetName = LiteralPathParameterSetName)] [Alias("Name")] [ValidateNotNull] - [ArgumentToModuleTransformationAttribute] + [ArgumentToModuleTransformation] [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] public PSModuleInfo[] Module { get; set; } diff --git a/src/System.Management.Automation/help/UpdatableHelpCommandBase.cs b/src/System.Management.Automation/help/UpdatableHelpCommandBase.cs index dad75a6b69c..687faa68246 100644 --- a/src/System.Management.Automation/help/UpdatableHelpCommandBase.cs +++ b/src/System.Management.Automation/help/UpdatableHelpCommandBase.cs @@ -179,13 +179,13 @@ static UpdatableHelpCommandBase() // NOTE: The HelpInfoUri must be updated with each release. - s_metadataCache.Add("Microsoft.PowerShell.Diagnostics", "https://aka.ms/powershell73-help"); - s_metadataCache.Add("Microsoft.PowerShell.Core", "https://aka.ms/powershell73-help"); - s_metadataCache.Add("Microsoft.PowerShell.Utility", "https://aka.ms/powershell73-help"); - s_metadataCache.Add("Microsoft.PowerShell.Host", "https://aka.ms/powershell73-help"); - s_metadataCache.Add("Microsoft.PowerShell.Management", "https://aka.ms/powershell73-help"); - s_metadataCache.Add("Microsoft.PowerShell.Security", "https://aka.ms/powershell73-help"); - s_metadataCache.Add("Microsoft.WSMan.Management", "https://aka.ms/powershell73-help"); + s_metadataCache.Add("Microsoft.PowerShell.Diagnostics", "https://aka.ms/powershell75-help"); + s_metadataCache.Add("Microsoft.PowerShell.Core", "https://aka.ms/powershell75-help"); + s_metadataCache.Add("Microsoft.PowerShell.Utility", "https://aka.ms/powershell75-help"); + s_metadataCache.Add("Microsoft.PowerShell.Host", "https://aka.ms/powershell75-help"); + s_metadataCache.Add("Microsoft.PowerShell.Management", "https://aka.ms/powershell75-help"); + s_metadataCache.Add("Microsoft.PowerShell.Security", "https://aka.ms/powershell75-help"); + s_metadataCache.Add("Microsoft.WSMan.Management", "https://aka.ms/powershell75-help"); } /// <summary> 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/CoreCommandContext.cs b/src/System.Management.Automation/namespaces/CoreCommandContext.cs index 9ed0e8be95c..cf8c43c4b3e 100644 --- a/src/System.Management.Automation/namespaces/CoreCommandContext.cs +++ b/src/System.Management.Automation/namespaces/CoreCommandContext.cs @@ -29,7 +29,7 @@ internal sealed class CmdletProviderContext /// An instance of the PSTraceSource class used for trace output /// using "CmdletProviderContext" as the category. /// </summary> - [Dbg.TraceSourceAttribute( + [Dbg.TraceSource( "CmdletProviderContext", "The context under which a core command is being run.")] private static readonly Dbg.PSTraceSource s_tracer = diff --git a/src/System.Management.Automation/namespaces/FileSystemContentStream.cs b/src/System.Management.Automation/namespaces/FileSystemContentStream.cs index 5aa81ccd680..d0f8f08deab 100644 --- a/src/System.Management.Automation/namespaces/FileSystemContentStream.cs +++ b/src/System.Management.Automation/namespaces/FileSystemContentStream.cs @@ -35,7 +35,7 @@ internal class FileSystemContentReaderWriter : IContentReader, IContentWriter /// An instance of the PSTraceSource class used for trace output /// using "FileSystemContentStream" as the category. /// </summary> - [Dbg.TraceSourceAttribute( + [Dbg.TraceSource( "FileSystemContentStream", "The provider content reader and writer for the file system")] private static readonly Dbg.PSTraceSource s_tracer = @@ -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 d22fcdc88fb..05c8fff76e0 100644 --- a/src/System.Management.Automation/namespaces/FileSystemProvider.cs +++ b/src/System.Management.Automation/namespaces/FileSystemProvider.cs @@ -71,7 +71,7 @@ public sealed partial class FileSystemProvider : NavigationCmdletProvider, /// An instance of the PSTraceSource class used for trace output /// using "FileSystemProvider" as the category. /// </summary> - [Dbg.TraceSourceAttribute("FileSystemProvider", "The namespace navigation provider for the file system")] + [Dbg.TraceSource("FileSystemProvider", "The namespace navigation provider for the file system")] private static readonly Dbg.PSTraceSource s_tracer = Dbg.PSTraceSource.GetTracer("FileSystemProvider", "The namespace navigation provider for the file system"); @@ -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; } @@ -1086,11 +1086,10 @@ protected override bool IsValidPath(string path) // Remove drive root first string pathWithoutDriveRoot = path.Substring(Path.GetPathRoot(path).Length); - char[] invalidFileChars = Path.GetInvalidFileNameChars(); foreach (string segment in pathWithoutDriveRoot.Split(Path.DirectorySeparatorChar)) { - if (segment.IndexOfAny(invalidFileChars) != -1) + if (PathUtils.ContainsInvalidFileNameChars(segment)) { return false; } @@ -1228,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); @@ -1325,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; @@ -1903,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); } @@ -2258,24 +2282,21 @@ protected override void NewItem( { exists = true; - var normalizedTargetPath = strTargetPath; - if (strTargetPath.StartsWith(".\\", StringComparison.OrdinalIgnoreCase) || - strTargetPath.StartsWith("./", StringComparison.OrdinalIgnoreCase)) - { - normalizedTargetPath = Path.Join(SessionState.Internal.CurrentLocation.ProviderPath, strTargetPath.AsSpan(2)); - } + // unify directory separators to be consistent with the rest of PowerShell even on non-Windows platforms; + // do this before resolving the target, otherwise e.g. `.\test` would break on Linux, since the combined + // path below would be something like `/path/to/cwd/.\test` + strTargetPath = strTargetPath.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator); + // check if the target is a file or directory + var normalizedTargetPath = Path.Combine(Path.GetDirectoryName(path), strTargetPath); GetFileSystemInfo(normalizedTargetPath, out isDirectory); - - strTargetPath = strTargetPath.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator); } else { // 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; @@ -2669,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)) { @@ -2704,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) { @@ -2719,8 +2734,15 @@ private void CreateDirectory(string path, bool streamOutput) } catch (IOException ioException) { - // Ignore the error if force was specified +#if UNIX if (!Force) +#else + // Windows error code for invalid characters in file or directory name + const int ERROR_INVALID_NAME = unchecked((int)0x8007007B); + + // Do not suppress IOException on Windows if it has the specific HResult for invalid characters in directory name + if (ioException.HResult == ERROR_INVALID_NAME || !Force) +#endif { // IOException contains specific message about the error occurred and so no need for errordetails. WriteError(new ErrorRecord(ioException, "CreateDirectoryIOError", ErrorCategory.WriteError, path)); @@ -7435,8 +7457,8 @@ internal FileSystemContentDynamicParametersBase(FileSystemProvider provider) /// reading data from the file. /// </summary> [Parameter] - [ArgumentToEncodingTransformationAttribute()] - [ArgumentEncodingCompletionsAttribute] + [ArgumentToEncodingTransformation] + [ArgumentEncodingCompletions] [ValidateNotNullOrEmpty] public Encoding Encoding { @@ -8203,8 +8225,7 @@ internal static List<AlternateStreamData> GetStreams(string path) AlternateStreamData data = new AlternateStreamData(); data.Stream = findStreamData.Name; data.Length = findStreamData.Length; - data.FileName = path.Replace(data.Stream, string.Empty); - data.FileName = data.FileName.Trim(':'); + data.FileName = path; alternateStreams.Add(data); findStreamData = new AlternateStreamNativeData(); @@ -8289,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 6cf082fc1f6..65c28f1586d 100644 --- a/src/System.Management.Automation/namespaces/LocationGlobber.cs +++ b/src/System.Management.Automation/namespaces/LocationGlobber.cs @@ -22,7 +22,7 @@ internal sealed class LocationGlobber /// An instance of the PSTraceSource class used for trace output /// using "LocationGlobber" as the category. /// </summary> - [Dbg.TraceSourceAttribute( + [Dbg.TraceSource( "LocationGlobber", "The location globber converts PowerShell paths with glob characters to zero or more paths.")] private static readonly Dbg.PSTraceSource s_tracer = @@ -32,7 +32,7 @@ internal sealed class LocationGlobber /// <summary> /// User level tracing for path resolution. /// </summary> - [Dbg.TraceSourceAttribute( + [Dbg.TraceSource( "PathResolution", "Traces the path resolution algorithm.")] private static readonly Dbg.PSTraceSource s_pathResolutionTracer = @@ -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 259bfd7fee9..4345ec9f8ec 100644 --- a/src/System.Management.Automation/namespaces/ProviderBase.cs +++ b/src/System.Management.Automation/namespaces/ProviderBase.cs @@ -76,7 +76,7 @@ public abstract partial class CmdletProvider : IResourceSupplier /// An instance of the PSTraceSource class used for trace output /// using "CmdletProviderClasses" as the category. /// </summary> - [TraceSourceAttribute( + [TraceSource( "CmdletProviderClasses", "The namespace provider base classes tracer")] internal static readonly PSTraceSource providerBaseTracer = PSTraceSource.GetTracer( @@ -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 ecf36bec4ca..4e3b84716cd 100644 --- a/src/System.Management.Automation/namespaces/RegistryProvider.cs +++ b/src/System.Management.Automation/namespaces/RegistryProvider.cs @@ -1825,8 +1825,19 @@ public void GetProperty( notePropertyName = LocalizedDefaultToken; } - propertyResults.Properties.Add(new PSNoteProperty(notePropertyName, key.GetValue(valueName))); - valueAdded = true; + try + { + propertyResults.Properties.Add(new PSNoteProperty(notePropertyName, key.GetValue(valueName))); + valueAdded = true; + } + catch (InvalidCastException invalidCast) + { + WriteError(new ErrorRecord( + invalidCast, + invalidCast.GetType().FullName, + ErrorCategory.ReadError, + path)); + } } key.Close(); diff --git a/src/System.Management.Automation/namespaces/SessionStateProviderBase.cs b/src/System.Management.Automation/namespaces/SessionStateProviderBase.cs index 3f0f52c942e..e4199291437 100644 --- a/src/System.Management.Automation/namespaces/SessionStateProviderBase.cs +++ b/src/System.Management.Automation/namespaces/SessionStateProviderBase.cs @@ -26,7 +26,7 @@ public abstract class SessionStateProviderBase : ContainerCmdletProvider, IConte /// <summary> /// An instance of the PSTraceSource class used for trace output. /// </summary> - [Dbg.TraceSourceAttribute( + [Dbg.TraceSource( "SessionStateProvider", "Providers that produce a view of session state data.")] private static readonly Dbg.PSTraceSource s_tracer = 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 3814058bd0e..4f2c65c49f5 100644 --- a/src/System.Management.Automation/namespaces/Win32Native.cs +++ b/src/System.Management.Automation/namespaces/Win32Native.cs @@ -27,7 +27,7 @@ namespace Microsoft.PowerShell.Commands.Internal // Remove the default demands for all N/Direct methods with this // global declaration on the class. // - [SuppressUnmanagedCodeSecurityAttribute] + [SuppressUnmanagedCodeSecurity] internal static class Win32Native { #region Integer Const @@ -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 da56deb4598..b5905f1cf7a 100644 --- a/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx +++ b/src/System.Management.Automation/resources/RemotingErrorIdStrings.resx @@ -123,6 +123,9 @@ <data name="OutOfMemory" xml:space="preserve"> <value>Out of process memory.</value> </data> + <data name="UnsupportedOSForRemoteEnumeration" xml:space="preserve"> + <value>Remote PSSession enumeration with -ComputerName is only supported on Windows and not "{0}".</value> + </data> <data name="PipelineIdsDoNotMatch" xml:space="preserve"> <value>Pipeline ID "{0}" does not match the InstanceId of the pipeline that is currently running, "{1}".</value> </data> @@ -846,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> @@ -1408,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> @@ -1723,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 ea249db55e7..39fbc3469f2 100644 --- a/src/System.Management.Automation/resources/SuggestionStrings.resx +++ b/src/System.Management.Automation/resources/SuggestionStrings.resx @@ -126,10 +126,4 @@ If you trust this command, run the following command instead:</value> <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 26d4dc4e14e..298de92da3c 100644 --- a/src/System.Management.Automation/resources/TabCompletionStrings.resx +++ b/src/System.Management.Automation/resources/TabCompletionStrings.resx @@ -329,6 +329,218 @@ <data name="shrOperatorDescription" xml:space="preserve"> <value>Shift Right bit operator. Inserts zero in the left-most bit position. For signed values, sign bit is preserved.</value> </data> + <data name="NameHashtableKeyDescription" xml:space="preserve"> + <value>[string] +Specifies the name of the property being created.</value> + </data> + <data name="LabelHashtableKeyDescription" xml:space="preserve"> + <value>[string] +Specifies the name of the property being created.</value> + </data> + <data name="ExpressionHashtableKeyDescription" xml:space="preserve"> + <value>[scriptblock] +A script block used to calculate the value of the new property.</value> + </data> + <data name="AlignmentHashtableKeyDescription" xml:space="preserve"> + <value>[string] +Define how the values are displayed in a column. +Valid values are 'left', 'center', or 'right'.</value> + </data> + <data name="FormatStringHashtableKeyDescription" xml:space="preserve"> + <value>[string] +Specifies a format string that defines how the value is formatted for output.</value> + </data> + <data name="WidthHashtableKeyDescription" xml:space="preserve"> + <value>[int] +Specifies the maximum column width in a table when the value is displayed. +The value must be greater than 0.</value> + </data> + <data name="DepthHashtableKeyDescription" xml:space="preserve"> + <value>[int] +The depth key specifies the depth of expansion per property.</value> + </data> + <data name="AscendingHashtableKeyDescription" xml:space="preserve"> + <value>[bool] +Specifies the order of sorting for one or more properties.</value> + </data> + <data name="DescendingHashtableKeyDescription" xml:space="preserve"> + <value>[bool] +Specifies the order of sorting for one or more properties.</value> + </data> + <data name="LogNameHashtableKeyDescription" xml:space="preserve"> + <value>[String[]] +Specifies the log names to get events from. +Supports wildcards.</value> + </data> + <data name="ProviderNameHashtableKeyDescription" xml:space="preserve"> + <value>[String[]] +Specifies the event log providers to get events from. +Supports wildcards.</value> + </data> + <data name="PathHashtableKeyDescription" xml:space="preserve"> + <value>[String[]] +Specifies file paths to log files to get events from. +Valid file formats are: .etl, .evt, and .evtx</value> + </data> + <data name="KeywordsHashtableKeyDescription" xml:space="preserve"> + <value>[Long[]] +Selects events with the specified keyword bitmasks. +The following are standard keywords: +4503599627370496: AuditFailure +9007199254740992: AuditSuccess +4503599627370496: CorrelationHint +18014398509481984: CorrelationHint2 +36028797018963968: EventLogClassic +281474976710656: ResponseTime +2251799813685248: Sqm +562949953421312: WdiContext +1125899906842624: WdiDiagnostic</value> + </data> + <data name="IDHashtableKeyDescription" xml:space="preserve"> + <value>[int[]] +Selects events with the specified event IDs.</value> + </data> + <data name="LevelHashtableKeyDescription" xml:space="preserve"> + <value>[int[]] +Selects events with the specified log levels. +The following log levels are valid: +1: Critical +2: Error +3: Warning +4: Informational +5: Verbose</value> + </data> + <data name="StartTimeHashtableKeyDescription" xml:space="preserve"> + <value>[datetime] +Selects events created after the specified date and time.</value> + </data> + <data name="EndTimeHashtableKeyDescription" xml:space="preserve"> + <value>[datetime] +Selects events created before the specified date and time.</value> + </data> + <data name="UserIDHashtableKeyDescription" xml:space="preserve"> + <value>[string] +Selects events generated by the specified user. +This can either be a string representation of a SID or a domain and username in the format DOMAIN\USERNAME or USERNAME@DOMAIN</value> + </data> + <data name="DataHashtableKeyDescription" xml:space="preserve"> + <value>[string[]] +Selects events with any of the specified values in the EventData section.</value> + </data> + <data name="SuppressHashFilterHashtableKeyDescription" xml:space="preserve"> + <value>[hashtable] +Excludes events that match the values specified in the hashtable.</value> + </data> + <data name="RequiresModulesParameterDescription" xml:space="preserve"> + <value>[string] or [hashtable] +Specifies an array of PowerShell modules that the script requires. +Each element can either be a string with the module name as value or a hashtable with the following keys: +Name: Name of the module +GUID: GUID of the module +One of the following: +ModuleVersion: Specifies a minimum acceptable version of the module. +RequiredVersion: Specifies an exact, required version of the module. +MaximumVersion: Specifies the maximum acceptable version of the module.</value> + </data> + <data name="RequiresPSEditionParameterDescription" xml:space="preserve"> + <value>[string] +Specifies a PowerShell edition that the script requires. +Valid values are "Core" and "Desktop"</value> + </data> + <data name="RequiresRunAsAdministratorParameterDescription" xml:space="preserve"> + <value>[switch] +Specifies that PowerShell must be running as administrator on Windows. +This must be the last parameter on the #requires statement line.</value> + </data> + <data name="RequiresVersionParameterDescription" xml:space="preserve"> + <value>[version] +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 7+ to run.</value> + </data> + <data name="RequiresPsEditionDesktopDescription" xml:space="preserve"> + <value>Specifies that the script requires Windows PowerShell 5.1 to run.</value> + </data> + <data name="RequiresModuleSpecModuleNameDescription" xml:space="preserve"> + <value>[string] +Required. Specifies the module name.</value> + </data> + <data name="RequiresModuleSpecGUIDDescription" xml:space="preserve"> + <value>[string] +Optional. Specifies the GUID of the module.</value> + </data> + <data name="RequiresModuleSpecModuleVersionDescription" xml:space="preserve"> + <value>[string] +Specifies a minimum acceptable version of the module.</value> + </data> + <data name="RequiresModuleSpecRequiredVersionDescription" xml:space="preserve"> + <value>[string] +Specifies an exact, required version of the module.</value> + </data> + <data name="RequiresModuleSpecMaximumVersionDescription" xml:space="preserve"> + <value>[string] +Specifies the maximum acceptable version of the module.</value> + </data> + <data name="CommentHelpSYNOPSISKeywordDescription" xml:space="preserve"> + <value>A brief description of the function or script. +This keyword can be used only once in each topic.</value> + </data> + <data name="CommentHelpDESCRIPTIONKeywordDescription" xml:space="preserve"> + <value>A detailed description of the function or script. +This keyword can be used only once in each topic.</value> + </data> + <data name="CommentHelpPARAMETERKeywordDescription" xml:space="preserve"> + <value>.PARAMETER <Parameter-Name> +The description of a parameter. +Add a .PARAMETER keyword for each parameter in the function or script syntax.</value> + </data> + <data name="CommentHelpEXAMPLEKeywordDescription" xml:space="preserve"> + <value>A sample command that uses the function or script, optionally followed by sample output and a description. +Repeat this keyword for each example.</value> + </data> + <data name="CommentHelpINPUTSKeywordDescription" xml:space="preserve"> + <value>The .NET types of objects that can be piped to the function or script. +You can also include a description of the input objects.</value> + </data> + <data name="CommentHelpOUTPUTSKeywordDescription" xml:space="preserve"> + <value>The .NET type of the objects that the cmdlet returns. +You can also include a description of the returned objects.</value> + </data> + <data name="CommentHelpNOTESKeywordDescription" xml:space="preserve"> + <value>Additional information about the function or script.</value> + </data> + <data name="CommentHelpLINKKeywordDescription" xml:space="preserve"> + <value>The name of a related topic. +Repeat the .LINK keyword for each related topic. +The .Link keyword content can also include a URI to an online version of the same help topic.</value> + </data> + <data name="CommentHelpCOMPONENTKeywordDescription" xml:space="preserve"> + <value>The name of the technology or feature that the function or script uses, or to which it is related.</value> + </data> + <data name="CommentHelpROLEKeywordDescription" xml:space="preserve"> + <value>The name of the user role for the help topic.</value> + </data> + <data name="CommentHelpFUNCTIONALITYKeywordDescription" xml:space="preserve"> + <value>The keywords that describe the intended use of the function.</value> + </data> + <data name="CommentHelpFORWARDHELPTARGETNAMEKeywordDescription" xml:space="preserve"> + <value>.FORWARDHELPTARGETNAME <Command-Name> +Redirects to the help topic for the specified command.</value> + </data> + <data name="CommentHelpFORWARDHELPCATEGORYKeywordDescription" xml:space="preserve"> + <value>.FORWARDHELPCATEGORY <Category> +Specifies the help category of the item in .ForwardHelpTargetName</value> + </data> + <data name="CommentHelpREMOTEHELPRUNSPACEKeywordDescription" xml:space="preserve"> + <value>.REMOTEHELPRUNSPACE <PSSession-variable> +Specifies a session that contains the help topic. +Enter a variable that contains a PSSession object.</value> + </data> + <data name="CommentHelpEXTERNALHELPKeywordDescription" xml:space="preserve"> + <value>.EXTERNALHELP <XML Help File> +The .ExternalHelp keyword is required when a function or script is documented in XML files.</value> + </data> <data name="AssemblyKeywordDescription" xml:space="preserve"> <value>Specifies the path to a .NET assembly to load. @@ -353,4 +565,46 @@ using namespace <AliasName> = <.NET-namespace></value> using type <AliasName> = <.NET-type></value> </data> + <data name="RegistryStringToolTip" xml:space="preserve"> + <value>A normal string.</value> + </data> + <data name="RegistryExpandStringToolTip" xml:space="preserve"> + <value>A string that contains unexpanded references to environment variables that are expanded when the value is retrieved.</value> + </data> + <data name="RegistryBinaryToolTip" xml:space="preserve"> + <value>Binary data in any form.</value> + </data> + <data name="RegistryDWordToolTip" xml:space="preserve"> + <value>A 32-bit binary number.</value> + </data> + <data name="RegistryMultiStringToolTip" xml:space="preserve"> + <value>An array of strings.</value> + </data> + <data name="RegistryQWordToolTip" xml:space="preserve"> + <value>A 64-bit binary number.</value> + </data> + <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 0c563eb46b3..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; @@ -1703,29 +1702,29 @@ internal enum AMSI_RESULT /// Return Type: HRESULT->LONG->int ///appName: LPCWSTR->WCHAR* ///amsiContext: HAMSICONTEXT* - [DefaultDllImportSearchPathsAttribute(DllImportSearchPath.System32)] - [DllImportAttribute("amsi.dll", EntryPoint = "AmsiInitialize", CallingConvention = CallingConvention.StdCall)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [DllImport("amsi.dll", EntryPoint = "AmsiInitialize", CallingConvention = CallingConvention.StdCall)] internal static extern int AmsiInitialize( - [InAttribute()][MarshalAsAttribute(UnmanagedType.LPWStr)] string appName, ref System.IntPtr amsiContext); + [In][MarshalAs(UnmanagedType.LPWStr)] string appName, ref System.IntPtr amsiContext); /// Return Type: void ///amsiContext: HAMSICONTEXT->HAMSICONTEXT__* - [DefaultDllImportSearchPathsAttribute(DllImportSearchPath.System32)] - [DllImportAttribute("amsi.dll", EntryPoint = "AmsiUninitialize", CallingConvention = CallingConvention.StdCall)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [DllImport("amsi.dll", EntryPoint = "AmsiUninitialize", CallingConvention = CallingConvention.StdCall)] internal static extern void AmsiUninitialize(System.IntPtr amsiContext); /// Return Type: HRESULT->LONG->int ///amsiContext: HAMSICONTEXT->HAMSICONTEXT__* ///amsiSession: HAMSISESSION* - [DefaultDllImportSearchPathsAttribute(DllImportSearchPath.System32)] - [DllImportAttribute("amsi.dll", EntryPoint = "AmsiOpenSession", CallingConvention = CallingConvention.StdCall)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [DllImport("amsi.dll", EntryPoint = "AmsiOpenSession", CallingConvention = CallingConvention.StdCall)] internal static extern int AmsiOpenSession(System.IntPtr amsiContext, ref System.IntPtr amsiSession); /// Return Type: void ///amsiContext: HAMSICONTEXT->HAMSICONTEXT__* ///amsiSession: HAMSISESSION->HAMSISESSION__* - [DefaultDllImportSearchPathsAttribute(DllImportSearchPath.System32)] - [DllImportAttribute("amsi.dll", EntryPoint = "AmsiCloseSession", CallingConvention = CallingConvention.StdCall)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [DllImport("amsi.dll", EntryPoint = "AmsiCloseSession", CallingConvention = CallingConvention.StdCall)] internal static extern void AmsiCloseSession(System.IntPtr amsiContext, System.IntPtr amsiSession); /// Return Type: HRESULT->LONG->int @@ -1735,13 +1734,13 @@ internal static extern int AmsiInitialize( ///contentName: LPCWSTR->WCHAR* ///amsiSession: HAMSISESSION->HAMSISESSION__* ///result: AMSI_RESULT* - [DefaultDllImportSearchPathsAttribute(DllImportSearchPath.System32)] - [DllImportAttribute("amsi.dll", EntryPoint = "AmsiScanBuffer", CallingConvention = CallingConvention.StdCall)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [DllImport("amsi.dll", EntryPoint = "AmsiScanBuffer", CallingConvention = CallingConvention.StdCall)] internal static extern int AmsiScanBuffer( System.IntPtr amsiContext, System.IntPtr buffer, uint length, - [InAttribute()][MarshalAsAttribute(UnmanagedType.LPWStr)] string contentName, + [In][MarshalAs(UnmanagedType.LPWStr)] string contentName, System.IntPtr amsiSession, ref AMSI_RESULT result); @@ -1751,13 +1750,13 @@ internal static extern int AmsiScanBuffer( /// length: ULONG->unsigned int /// contentName: LPCWSTR->WCHAR* /// result: AMSI_RESULT* - [DefaultDllImportSearchPathsAttribute(DllImportSearchPath.System32)] - [DllImportAttribute("amsi.dll", EntryPoint = "AmsiNotifyOperation", CallingConvention = CallingConvention.StdCall)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [DllImport("amsi.dll", EntryPoint = "AmsiNotifyOperation", CallingConvention = CallingConvention.StdCall)] internal static extern int AmsiNotifyOperation( System.IntPtr amsiContext, System.IntPtr buffer, uint length, - [InAttribute()][MarshalAsAttribute(UnmanagedType.LPWStr)] string contentName, + [In][MarshalAs(UnmanagedType.LPWStr)] string contentName, ref AMSI_RESULT result); /// Return Type: HRESULT->LONG->int @@ -1766,11 +1765,11 @@ internal static extern int AmsiNotifyOperation( ///contentName: LPCWSTR->WCHAR* ///amsiSession: HAMSISESSION->HAMSISESSION__* ///result: AMSI_RESULT* - [DefaultDllImportSearchPathsAttribute(DllImportSearchPath.System32)] - [DllImportAttribute("amsi.dll", EntryPoint = "AmsiScanString", CallingConvention = CallingConvention.StdCall)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + [DllImport("amsi.dll", EntryPoint = "AmsiScanString", CallingConvention = CallingConvention.StdCall)] internal static extern int AmsiScanString( - System.IntPtr amsiContext, [InAttribute()][MarshalAsAttribute(UnmanagedType.LPWStr)] string @string, - [InAttribute()][MarshalAsAttribute(UnmanagedType.LPWStr)] string contentName, System.IntPtr amsiSession, ref AMSI_RESULT result); + System.IntPtr amsiContext, [In][MarshalAs(UnmanagedType.LPWStr)] string @string, + [In][MarshalAs(UnmanagedType.LPWStr)] string contentName, System.IntPtr amsiSession, ref AMSI_RESULT result); } } } diff --git a/src/System.Management.Automation/security/Win32Native/WinTrust.cs b/src/System.Management.Automation/security/Win32Native/WinTrust.cs index c3b7bb85d91..a0ae8bc0e99 100644 --- a/src/System.Management.Automation/security/Win32Native/WinTrust.cs +++ b/src/System.Management.Automation/security/Win32Native/WinTrust.cs @@ -94,10 +94,10 @@ internal struct CRYPT_ATTR_BLOB public IntPtr pbData; } - [StructLayoutAttribute(LayoutKind.Sequential)] + [StructLayout(LayoutKind.Sequential)] internal struct CRYPT_ALGORITHM_IDENTIFIER { - [MarshalAsAttribute(UnmanagedType.LPStr)] public string pszObjId; + [MarshalAs(UnmanagedType.LPStr)] public string pszObjId; public CRYPT_ATTR_BLOB Parameters; } @@ -158,7 +158,7 @@ internal struct CRYPTCATSTORE public IntPtr hSorted; } - [StructLayoutAttribute(LayoutKind.Sequential)] + [StructLayout(LayoutKind.Sequential)] internal struct WINTRUST_DATA { public uint cbStruct; @@ -184,7 +184,7 @@ internal struct WINTRUST_FILE_INFO public IntPtr pgKnownSubject; } - [StructLayoutAttribute(LayoutKind.Sequential)] + [StructLayout(LayoutKind.Sequential)] internal struct WINTRUST_BLOB_INFO { public uint cbStruct; diff --git a/src/System.Management.Automation/security/nativeMethods.cs b/src/System.Management.Automation/security/nativeMethods.cs index b1c907f1fe2..6dd8f59d613 100644 --- a/src/System.Management.Automation/security/nativeMethods.cs +++ b/src/System.Management.Automation/security/nativeMethods.cs @@ -653,11 +653,11 @@ internal struct CRYPT_OID_INFO public uint cbSize; /// LPCSTR->CHAR* - [MarshalAsAttribute(UnmanagedType.LPStr)] + [MarshalAs(UnmanagedType.LPStr)] public string pszOID; /// LPCWSTR->WCHAR* - [MarshalAsAttribute(UnmanagedType.LPWStr)] + [MarshalAs(UnmanagedType.LPWStr)] public string pwszName; /// DWORD->unsigned int @@ -898,7 +898,7 @@ internal enum SIGNATURE_INFO_TYPE SIT_CATALOG, } - [StructLayoutAttribute(LayoutKind.Sequential)] + [StructLayout(LayoutKind.Sequential)] internal struct SIGNATURE_INFO { /// DWORD->unsigned int @@ -917,21 +917,21 @@ internal struct SIGNATURE_INFO internal uint dwInfoAvailability; /// PWSTR->WCHAR* - [MarshalAsAttribute(UnmanagedType.LPWStr)] + [MarshalAs(UnmanagedType.LPWStr)] internal string pszDisplayName; /// DWORD->unsigned int internal uint cchDisplayName; /// PWSTR->WCHAR* - [MarshalAsAttribute(UnmanagedType.LPWStr)] + [MarshalAs(UnmanagedType.LPWStr)] internal string pszPublisherName; /// DWORD->unsigned int internal uint cchPublisherName; /// PWSTR->WCHAR* - [MarshalAsAttribute(UnmanagedType.LPWStr)] + [MarshalAs(UnmanagedType.LPWStr)] internal string pszMoreInfoURL; /// DWORD->unsigned int @@ -947,7 +947,7 @@ internal struct SIGNATURE_INFO internal int fOSBinary; } - [StructLayoutAttribute(LayoutKind.Sequential)] + [StructLayout(LayoutKind.Sequential)] internal struct CERT_INFO { /// DWORD->unsigned int @@ -987,18 +987,18 @@ internal struct CERT_INFO internal System.IntPtr rgExtension; } - [StructLayoutAttribute(LayoutKind.Sequential)] + [StructLayout(LayoutKind.Sequential)] internal struct CRYPT_ALGORITHM_IDENTIFIER { /// LPSTR->CHAR* - [MarshalAsAttribute(UnmanagedType.LPStr)] + [MarshalAs(UnmanagedType.LPStr)] internal string pszObjId; /// CRYPT_OBJID_BLOB->_CRYPTOAPI_BLOB internal CRYPT_ATTR_BLOB Parameters; } - [StructLayoutAttribute(LayoutKind.Sequential)] + [StructLayout(LayoutKind.Sequential)] internal struct FILETIME { /// DWORD->unsigned int @@ -1008,7 +1008,7 @@ internal struct FILETIME internal uint dwHighDateTime; } - [StructLayoutAttribute(LayoutKind.Sequential)] + [StructLayout(LayoutKind.Sequential)] internal struct CERT_PUBLIC_KEY_INFO { /// CRYPT_ALGORITHM_IDENTIFIER->_CRYPT_ALGORITHM_IDENTIFIER @@ -1018,7 +1018,7 @@ internal struct CERT_PUBLIC_KEY_INFO internal CRYPT_BIT_BLOB PublicKey; } - [StructLayoutAttribute(LayoutKind.Sequential)] + [StructLayout(LayoutKind.Sequential)] internal struct CRYPT_BIT_BLOB { /// DWORD->unsigned int @@ -1031,11 +1031,11 @@ internal struct CRYPT_BIT_BLOB internal uint cUnusedBits; } - [StructLayoutAttribute(LayoutKind.Sequential)] + [StructLayout(LayoutKind.Sequential)] internal struct CERT_EXTENSION { /// LPSTR->CHAR* - [MarshalAsAttribute(UnmanagedType.LPStr)] + [MarshalAs(UnmanagedType.LPStr)] internal string pszObjId; /// BOOL->int @@ -1086,15 +1086,15 @@ internal static partial class NativeMethods ///pCodeProperties: PSAFER_CODE_PROPERTIES->_SAFER_CODE_PROPERTIES* ///pLevelHandle: SAFER_LEVEL_HANDLE* ///lpReserved: LPVOID->void* - [DllImportAttribute("advapi32.dll", EntryPoint = "SaferIdentifyLevel", SetLastError = true)] - [return: MarshalAsAttribute(UnmanagedType.Bool)] + [DllImport("advapi32.dll", EntryPoint = "SaferIdentifyLevel", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool SaferIdentifyLevel( uint dwNumProperties, - [InAttribute()] + [In] ref SAFER_CODE_PROPERTIES pCodeProperties, out IntPtr pLevelHandle, - [InAttribute()] - [MarshalAsAttribute(UnmanagedType.LPWStr)] + [In] + [MarshalAs(UnmanagedType.LPWStr)] string bucket); /// Return Type: BOOL->int @@ -1103,12 +1103,12 @@ internal static extern bool SaferIdentifyLevel( ///OutAccessToken: PHANDLE->HANDLE* ///dwFlags: DWORD->unsigned int ///lpReserved: LPVOID->void* - [DllImportAttribute("advapi32.dll", EntryPoint = "SaferComputeTokenFromLevel", SetLastError = true)] - [return: MarshalAsAttribute(UnmanagedType.Bool)] + [DllImport("advapi32.dll", EntryPoint = "SaferComputeTokenFromLevel", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool SaferComputeTokenFromLevel( - [InAttribute()] + [In] IntPtr LevelHandle, - [InAttribute()] + [In] System.IntPtr InAccessToken, ref System.IntPtr OutAccessToken, uint dwFlags, @@ -1116,18 +1116,18 @@ internal static extern bool SaferComputeTokenFromLevel( /// Return Type: BOOL->int ///hLevelHandle: SAFER_LEVEL_HANDLE->SAFER_LEVEL_HANDLE__* - [DllImportAttribute("advapi32.dll", EntryPoint = "SaferCloseLevel")] - [return: MarshalAsAttribute(UnmanagedType.Bool)] - internal static extern bool SaferCloseLevel([InAttribute()] IntPtr hLevelHandle); + [DllImport("advapi32.dll", EntryPoint = "SaferCloseLevel")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool SaferCloseLevel([In] IntPtr hLevelHandle); /// Return Type: BOOL->int ///hObject: HANDLE->void* - [DllImportAttribute(PinvokeDllNames.CloseHandleDllName, EntryPoint = "CloseHandle")] - [return: MarshalAsAttribute(UnmanagedType.Bool)] - internal static extern bool CloseHandle([InAttribute()] System.IntPtr hObject); + [DllImport(PinvokeDllNames.CloseHandleDllName, EntryPoint = "CloseHandle")] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool CloseHandle([In] System.IntPtr hObject); } - [StructLayoutAttribute(LayoutKind.Sequential)] + [StructLayout(LayoutKind.Sequential)] internal struct SAFER_CODE_PROPERTIES { /// DWORD->unsigned int @@ -1137,7 +1137,7 @@ internal struct SAFER_CODE_PROPERTIES public uint dwCheckFlags; /// LPCWSTR->WCHAR* - [MarshalAsAttribute(UnmanagedType.LPWStr)] + [MarshalAs(UnmanagedType.LPWStr)] public string ImagePath; /// HANDLE->void* @@ -1147,7 +1147,7 @@ internal struct SAFER_CODE_PROPERTIES public uint UrlZoneId; /// BYTE[SAFER_MAX_HASH_SIZE] - [MarshalAsAttribute( + [MarshalAs( UnmanagedType.ByValArray, SizeConst = NativeConstants.SAFER_MAX_HASH_SIZE, ArraySubType = UnmanagedType.I1)] @@ -1172,30 +1172,30 @@ internal struct SAFER_CODE_PROPERTIES public uint dwWVTUIChoice; } - [StructLayoutAttribute(LayoutKind.Explicit)] + [StructLayout(LayoutKind.Explicit)] internal struct LARGE_INTEGER { /// Anonymous_9320654f_2227_43bf_a385_74cc8c562686 - [FieldOffsetAttribute(0)] + [FieldOffset(0)] public Anonymous_9320654f_2227_43bf_a385_74cc8c562686 Struct1; /// Anonymous_947eb392_1446_4e25_bbd4_10e98165f3a9 - [FieldOffsetAttribute(0)] + [FieldOffset(0)] public Anonymous_947eb392_1446_4e25_bbd4_10e98165f3a9 u; /// LONGLONG->__int64 - [FieldOffsetAttribute(0)] + [FieldOffset(0)] public long QuadPart; } - [StructLayoutAttribute(LayoutKind.Sequential)] + [StructLayout(LayoutKind.Sequential)] internal struct HWND__ { /// int public int unused; } - [StructLayoutAttribute(LayoutKind.Sequential)] + [StructLayout(LayoutKind.Sequential)] internal struct Anonymous_9320654f_2227_43bf_a385_74cc8c562686 { /// DWORD->unsigned int @@ -1205,7 +1205,7 @@ internal struct Anonymous_9320654f_2227_43bf_a385_74cc8c562686 public int HighPart; } - [StructLayoutAttribute(LayoutKind.Sequential)] + [StructLayout(LayoutKind.Sequential)] internal struct Anonymous_947eb392_1446_4e25_bbd4_10e98165f3a9 { /// DWORD->unsigned int @@ -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/security/wldpNativeMethods.cs b/src/System.Management.Automation/security/wldpNativeMethods.cs index a59f37c0a8f..ab49f927614 100644 --- a/src/System.Management.Automation/security/wldpNativeMethods.cs +++ b/src/System.Management.Automation/security/wldpNativeMethods.cs @@ -6,6 +6,7 @@ // #if !UNIX +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Management.Automation.Internal; using System.Management.Automation.Runspaces; @@ -148,7 +149,7 @@ public static SystemEnforcementMode GetSystemLockdownPolicy() { lock (s_systemLockdownPolicyLock) { - s_systemLockdownPolicy = GetDebugLockdownPolicy(path: null); + s_systemLockdownPolicy = GetDebugLockdownPolicy(path: null, out _); } } @@ -172,93 +173,89 @@ public static SystemScriptFileEnforcement GetFilePolicyEnforcement( System.IO.FileStream fileStream) { SafeHandle fileHandle = fileStream.SafeFileHandle; - var systemLockdownPolicy = SystemPolicy.GetSystemLockdownPolicy(); + SystemEnforcementMode systemLockdownPolicy = GetSystemLockdownPolicy(); // First check latest WDAC APIs if available. - // Revert to legacy APIs if system policy is in AUDIT mode or debug hook is in effect. - Exception errorException = null; - if (s_wldpCanExecuteAvailable && systemLockdownPolicy == SystemEnforcementMode.Enforce) + if (systemLockdownPolicy is SystemEnforcementMode.Enforce + && s_wldpCanExecuteAvailable + && TryGetWldpCanExecuteFileResult(filePath, fileHandle, out SystemScriptFileEnforcement wldpFilePolicy)) { - try - { - string fileName = System.IO.Path.GetFileNameWithoutExtension(filePath); - string auditMsg = $"PowerShell ExternalScriptInfo reading file: {fileName}"; + return GetLockdownPolicy(filePath, fileHandle, wldpFilePolicy); + } - int hr = WldpNativeMethods.WldpCanExecuteFile( - host: PowerShellHost, - options: WLDP_EXECUTION_EVALUATION_OPTIONS.WLDP_EXECUTION_EVALUATION_OPTION_NONE, - fileHandle: fileHandle.DangerousGetHandle(), - auditInfo: auditMsg, - result: out WLDP_EXECUTION_POLICY canExecuteResult); + // Failed to invoke WldpCanExecuteFile, revert to legacy APIs. + if (systemLockdownPolicy is SystemEnforcementMode.None) + { + return SystemScriptFileEnforcement.None; + } - PSEtwLog.LogWDACQueryEvent("WldpCanExecuteFile", filePath, hr, (int)canExecuteResult); + // WldpCanExecuteFile was invoked successfully so we can skip running + // legacy WDAC APIs. AppLocker must still be checked in case it is more + // strict than the current WDAC policy. + return GetLockdownPolicy(filePath, fileHandle, canExecuteResult: null); + } - if (hr >= 0) - { - switch (canExecuteResult) - { - case WLDP_EXECUTION_POLICY.WLDP_CAN_EXECUTE_ALLOWED: - return SystemScriptFileEnforcement.Allow; + private static SystemScriptFileEnforcement ConvertToModernFileEnforcement(SystemEnforcementMode legacyMode) + { + return legacyMode switch + { + SystemEnforcementMode.None => SystemScriptFileEnforcement.Allow, + SystemEnforcementMode.Audit => SystemScriptFileEnforcement.AllowConstrainedAudit, + SystemEnforcementMode.Enforce => SystemScriptFileEnforcement.AllowConstrained, + _ => SystemScriptFileEnforcement.Block, + }; + } - case WLDP_EXECUTION_POLICY.WLDP_CAN_EXECUTE_BLOCKED: - return SystemScriptFileEnforcement.Block; + private static bool TryGetWldpCanExecuteFileResult(string filePath, SafeHandle fileHandle, out SystemScriptFileEnforcement result) + { + try + { + string fileName = System.IO.Path.GetFileNameWithoutExtension(filePath); + string auditMsg = $"PowerShell ExternalScriptInfo reading file: {fileName}"; - case WLDP_EXECUTION_POLICY.WLDP_CAN_EXECUTE_REQUIRE_SANDBOX: - return SystemScriptFileEnforcement.AllowConstrained; + int hr = WldpNativeMethods.WldpCanExecuteFile( + host: PowerShellHost, + options: WLDP_EXECUTION_EVALUATION_OPTIONS.WLDP_EXECUTION_EVALUATION_OPTION_NONE, + fileHandle: fileHandle.DangerousGetHandle(), + auditInfo: auditMsg, + result: out WLDP_EXECUTION_POLICY canExecuteResult); - default: - // Fall through to legacy system policy checks. - System.Diagnostics.Debug.Assert(false, $"Unknown execution policy returned from WldCanExecute: {canExecuteResult}"); - break; - } - } + PSEtwLog.LogWDACQueryEvent("WldpCanExecuteFile", filePath, hr, (int)canExecuteResult); - // If HResult is unsuccessful (such as E_NOTIMPL (0x80004001)), fall through to legacy system checks. - } - catch (DllNotFoundException ex) - { - // Fall back to legacy system policy checks. - s_wldpCanExecuteAvailable = false; - errorException = ex; - } - catch (EntryPointNotFoundException ex) + if (hr >= 0) { - // Fall back to legacy system policy checks. - s_wldpCanExecuteAvailable = false; - errorException = ex; + switch (canExecuteResult) + { + case WLDP_EXECUTION_POLICY.WLDP_CAN_EXECUTE_ALLOWED: + result = SystemScriptFileEnforcement.Allow; + return true; + + case WLDP_EXECUTION_POLICY.WLDP_CAN_EXECUTE_BLOCKED: + result = SystemScriptFileEnforcement.Block; + return true; + + case WLDP_EXECUTION_POLICY.WLDP_CAN_EXECUTE_REQUIRE_SANDBOX: + result = SystemScriptFileEnforcement.AllowConstrained; + return true; + + default: + // Fall through to legacy system policy checks. + Debug.Assert(false, $"Unknown policy result returned from WldCanExecute: {canExecuteResult}"); + break; + } } - if (errorException != null) - { - PSEtwLog.LogWDACQueryEvent("WldpCanExecuteFile_Failed", filePath, errorException.HResult, 0); - } + // If HResult is unsuccessful (such as E_NOTIMPL (0x80004001)), fall through to legacy system checks. } - - // Original (legacy) WDAC and AppLocker system checks. - if (systemLockdownPolicy == SystemEnforcementMode.None) + catch (Exception ex) when (ex is DllNotFoundException or EntryPointNotFoundException) { - return SystemScriptFileEnforcement.None; + // Fall back to legacy system policy checks. + s_wldpCanExecuteAvailable = false; + PSEtwLog.LogWDACQueryEvent("WldpCanExecuteFile_Failed", filePath, ex.HResult, 0); } - // Check policy for file. - switch (SystemPolicy.GetLockdownPolicy(filePath, fileHandle)) - { - case SystemEnforcementMode.Enforce: - // File is not allowed by policy enforcement and must run in CL mode. - return SystemScriptFileEnforcement.AllowConstrained; - - case SystemEnforcementMode.Audit: - // File is allowed but would be run in CL mode if policy was enforced and not audit. - return SystemScriptFileEnforcement.AllowConstrainedAudit; - - case SystemEnforcementMode.None: - // No restrictions, file will run in FL mode. - return SystemScriptFileEnforcement.Allow; - - default: - System.Diagnostics.Debug.Assert(false, "GetFilePolicyEnforcement: Unknown SystemEnforcementMode."); - return SystemScriptFileEnforcement.Block; - } + result = default; + return false; } /// <summary> @@ -267,9 +264,32 @@ public static SystemScriptFileEnforcement GetFilePolicyEnforcement( /// <returns>An EnforcementMode that describes policy.</returns> public static SystemEnforcementMode GetLockdownPolicy(string path, SafeHandle handle) { + SystemScriptFileEnforcement modernMode = GetLockdownPolicy(path, handle, canExecuteResult: null); + Debug.Assert( + modernMode is not SystemScriptFileEnforcement.Block, + "Block should never be converted to legacy file enforcement."); + + return modernMode switch + { + SystemScriptFileEnforcement.Block => SystemEnforcementMode.Enforce, + SystemScriptFileEnforcement.AllowConstrained => SystemEnforcementMode.Enforce, + SystemScriptFileEnforcement.AllowConstrainedAudit => SystemEnforcementMode.Audit, + SystemScriptFileEnforcement.Allow => SystemEnforcementMode.None, + SystemScriptFileEnforcement.None => SystemEnforcementMode.None, + _ => throw new ArgumentOutOfRangeException(nameof(modernMode)), + }; + } + + private static SystemScriptFileEnforcement GetLockdownPolicy( + string path, + SafeHandle handle, + SystemScriptFileEnforcement? canExecuteResult) + { + SystemScriptFileEnforcement wldpFilePolicy = canExecuteResult + ?? ConvertToModernFileEnforcement(GetWldpPolicy(path, handle)); + // Check the WLDP File policy via API - var wldpFilePolicy = GetWldpPolicy(path, handle); - if (wldpFilePolicy == SystemEnforcementMode.Enforce) + if (wldpFilePolicy is SystemScriptFileEnforcement.Block or SystemScriptFileEnforcement.AllowConstrained) { return wldpFilePolicy; } @@ -281,29 +301,28 @@ public static SystemEnforcementMode GetLockdownPolicy(string path, SafeHandle ha var appLockerFilePolicy = GetAppLockerPolicy(path, handle); if (appLockerFilePolicy == SystemEnforcementMode.Enforce) { - return appLockerFilePolicy; + return ConvertToModernFileEnforcement(appLockerFilePolicy); } // At this point, LockdownPolicy = Audit or Allowed. // If there was a WLDP policy, but WLDP didn't block it, // then it was explicitly allowed. Therefore, return the result for the file. - SystemEnforcementMode systemWldpPolicy = s_cachedWldpSystemPolicy.GetValueOrDefault(SystemEnforcementMode.None); - if ((systemWldpPolicy == SystemEnforcementMode.Audit) || - (systemWldpPolicy == SystemEnforcementMode.Enforce)) + if (s_cachedWldpSystemPolicy is SystemEnforcementMode.Audit or SystemEnforcementMode.Enforce + || wldpFilePolicy is SystemScriptFileEnforcement.AllowConstrainedAudit) { return wldpFilePolicy; } // If there was a system-wide AppLocker policy, but AppLocker didn't block it, // then return AppLocker's status. - if (s_cachedSaferSystemPolicy.GetValueOrDefault(SaferPolicy.Allowed) == - SaferPolicy.Disallowed) + if (s_cachedSaferSystemPolicy is SaferPolicy.Disallowed) { - return appLockerFilePolicy; + return ConvertToModernFileEnforcement(appLockerFilePolicy); } // If it's not set to 'Enforce' by the platform, allow debug overrides - return GetDebugLockdownPolicy(path); + GetDebugLockdownPolicy(path, out SystemScriptFileEnforcement debugPolicy); + return debugPolicy; } [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", @@ -558,7 +577,7 @@ private static SaferPolicy TestSaferPolicy(string testPathScript, string testPat return result; } - private static SystemEnforcementMode GetDebugLockdownPolicy(string path) + private static SystemEnforcementMode GetDebugLockdownPolicy(string path, out SystemScriptFileEnforcement modernEnforcement) { s_allowDebugOverridePolicy = true; @@ -569,10 +588,19 @@ private static SystemEnforcementMode GetDebugLockdownPolicy(string path) // check so that we can actually put it in the filename during testing. if (path.Contains("System32", StringComparison.OrdinalIgnoreCase)) { + modernEnforcement = SystemScriptFileEnforcement.Allow; return SystemEnforcementMode.None; } // No explicit debug allowance for the file, so return the system policy if there is one. + modernEnforcement = s_systemLockdownPolicy switch + { + SystemEnforcementMode.Enforce => SystemScriptFileEnforcement.AllowConstrained, + SystemEnforcementMode.Audit => SystemScriptFileEnforcement.AllowConstrainedAudit, + SystemEnforcementMode.None => SystemScriptFileEnforcement.None, + _ => SystemScriptFileEnforcement.None, + }; + return s_systemLockdownPolicy.GetValueOrDefault(SystemEnforcementMode.None); } @@ -582,10 +610,13 @@ private static SystemEnforcementMode GetDebugLockdownPolicy(string path) if (result != null) { pdwLockdownState = LanguagePrimitives.ConvertTo<uint>(result); - return GetLockdownPolicyForResult(pdwLockdownState); + SystemEnforcementMode policy = GetLockdownPolicyForResult(pdwLockdownState); + modernEnforcement = ConvertToModernFileEnforcement(policy); + return policy; } // If the system-wide debug policy had no preference, then there is no enforcement. + modernEnforcement = SystemScriptFileEnforcement.None; return SystemEnforcementMode.None; } 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/PathUtils.cs b/src/System.Management.Automation/utils/PathUtils.cs index 1d7c3140d69..3afea30a962 100644 --- a/src/System.Management.Automation/utils/PathUtils.cs +++ b/src/System.Management.Automation/utils/PathUtils.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Buffers; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; @@ -870,5 +871,37 @@ private static bool IsDirectorySeparator(char c) } #endregion + + #region Helpers for checking invalid paths using SearchValues + + /// <summary> + /// Contains characters that are invalid in file names. + /// </summary> + private static readonly SearchValues<char> s_invalidFileNameChars + = SearchValues.Create(Path.GetInvalidFileNameChars()); + + /// <summary> + /// Contains characters that are invalid in path names. + /// </summary> + private static readonly SearchValues<char> s_invalidPathChars + = SearchValues.Create(Path.GetInvalidPathChars()); + + /// <summary> + /// Checks if the specified filename contains any characters that are invalid in file names. + /// </summary> + /// <param name="filename">The path to check.</param> + /// <returns>True if the filename contains invalid file name characters, otherwise false.</returns> + internal static bool ContainsInvalidFileNameChars(ReadOnlySpan<char> filename) + => filename.ContainsAny(s_invalidFileNameChars); + + /// <summary> + /// Checks if the specified path contains any characters that are invalid in path names. + /// </summary> + /// <param name="path">The path to check.</param> + /// <returns>True if the path contains invalid path characters, otherwise false.</returns> + internal static bool ContainsInvalidPathChars(ReadOnlySpan<char> path) + => path.ContainsAny(s_invalidPathChars); + + #endregion } } diff --git a/src/System.Management.Automation/utils/PlatformInvokes.cs b/src/System.Management.Automation/utils/PlatformInvokes.cs index 4405f2f6910..f9e78f5d1d3 100644 --- a/src/System.Management.Automation/utils/PlatformInvokes.cs +++ b/src/System.Management.Automation/utils/PlatformInvokes.cs @@ -11,7 +11,7 @@ namespace System.Management.Automation internal static class PlatformInvokes { [StructLayout(LayoutKind.Sequential)] - internal class FILETIME + internal sealed class FILETIME { internal uint dwLowDateTime; internal uint dwHighDateTime; @@ -92,7 +92,7 @@ internal enum FileAttributes : uint } [StructLayout(LayoutKind.Sequential)] - internal class SecurityAttributes + internal sealed class SecurityAttributes { internal int nLength; internal SafeLocalMemHandle lpSecurityDescriptor; @@ -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); @@ -536,7 +530,7 @@ internal struct PRIVILEGE_SET internal static readonly UInt32 OPEN_EXISTING = 3; [StructLayout(LayoutKind.Sequential)] - internal class PROCESS_INFORMATION + internal sealed class PROCESS_INFORMATION { public IntPtr hProcess; public IntPtr hThread; @@ -581,7 +575,7 @@ private void Dispose(bool disposing) } [StructLayout(LayoutKind.Sequential)] - internal class STARTUPINFO + internal sealed class STARTUPINFO { public int cb; public IntPtr lpReserved; @@ -645,7 +639,7 @@ public void Dispose() } [StructLayout(LayoutKind.Sequential)] - internal class SECURITY_ATTRIBUTES + internal sealed class SECURITY_ATTRIBUTES { public int nLength; public SafeLocalMemHandle lpSecurityDescriptor; diff --git a/src/System.Management.Automation/utils/PsUtils.cs b/src/System.Management.Automation/utils/PsUtils.cs index 53809e586a0..0a4682bcdd6 100644 --- a/src/System.Management.Automation/utils/PsUtils.cs +++ b/src/System.Management.Automation/utils/PsUtils.cs @@ -74,12 +74,12 @@ internal static Process GetParentProcess(Process current) } /// <summary> - /// Return true/false to indicate whether the processor architecture is ARM. + /// Return true/false to indicate whether the process architecture is ARM. /// </summary> /// <returns></returns> - internal static bool IsRunningOnProcessorArchitectureARM() + internal static bool IsRunningOnProcessArchitectureARM() { - Architecture arch = RuntimeInformation.OSArchitecture; + Architecture arch = RuntimeInformation.ProcessArchitecture; return arch == Architecture.Arm || arch == Architecture.Arm64; } @@ -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 9e40e60a4d6..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,536 +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", - "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", - "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> @@ -834,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); } } @@ -854,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" @@ -1050,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; } @@ -1066,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/Verbs.cs b/src/System.Management.Automation/utils/Verbs.cs index f06dd2d19e0..72299ab3d84 100644 --- a/src/System.Management.Automation/utils/Verbs.cs +++ b/src/System.Management.Automation/utils/Verbs.cs @@ -1332,35 +1332,6 @@ static Verbs() /// <returns>Verb group display name.</returns> private static string GetVerbGroupDisplayName(Type verbType) => verbType.Name.Substring(5); - /// <summary> - /// Filters by verbs and commands. - /// </summary> - /// <param name="verbs">The array of verbs.</param> - /// <param name="commands">The collection of commands.</param> - /// <returns>List of Verbs.</returns> - private static IEnumerable<string> FilterByVerbsAndCommands(string[] verbs, Collection<CmdletInfo> commands) - { - if (commands is null || commands.Count == 0) - { - yield break; - } - - Collection<WildcardPattern> verbPatterns = SessionStateUtilities.CreateWildcardsFromStrings( - verbs, - WildcardOptions.IgnoreCase); - - foreach (CmdletInfo command in commands) - { - if (SessionStateUtilities.MatchesAnyWildcardPattern( - command.Verb, - verbPatterns, - defaultValue: false)) - { - yield return command.Verb; - } - } - } - /// <summary> /// Filters by verbs and groups. /// </summary> @@ -1384,10 +1355,7 @@ internal static IEnumerable<VerbInfo> FilterByVerbsAndGroups(string[] verbs, str foreach (Type verbType in VerbTypes) { - if (SessionStateUtilities.CollectionContainsValue( - groups, - GetVerbGroupDisplayName(verbType), - StringComparer.OrdinalIgnoreCase)) + if (GroupsContainVerbType(groups, verbType)) { foreach (VerbInfo verb in FilterVerbsByType(verbs, verbType)) { @@ -1397,6 +1365,62 @@ internal static IEnumerable<VerbInfo> FilterByVerbsAndGroups(string[] verbs, str } } + /// <summary> + /// Checks if verb type exists in list of groups. + /// </summary> + /// <param name="groups">The list of groups</param> + /// <param name="verbType">The verb type to check.</param> + /// <returns>True if verb type was found, False if not found.</returns> + private static bool GroupsContainVerbType(string[] groups, Type verbType) + => SessionStateUtilities.CollectionContainsValue( + groups, + GetVerbGroupDisplayName(verbType), + StringComparer.OrdinalIgnoreCase); + + /// <summary> + /// Enumerates field names from a Verb Type. + /// </summary> + /// <param name="verbType">The verb type.</param> + /// <returns>List of field names.</returns> + private static IEnumerable<string> EnumerateFieldNamesFromVerbType(Type verbType) + { + foreach (FieldInfo field in verbType.GetFields()) + { + if (field.IsLiteral) + { + yield return field.Name; + } + } + } + + /// <summary> + /// Enumerates field names from all Verb Types. + /// </summary> + /// <returns>List of field names.</returns> + private static IEnumerable<string> EnumerateFieldNamesFromAllVerbTypes() + { + foreach (Type verbType in VerbTypes) + { + foreach (string fieldName in EnumerateFieldNamesFromVerbType(verbType)) + { + yield return fieldName; + } + } + } + + /// <summary> + /// Enumerates command verb names. + /// </summary> + /// <param name="commands">The collection of commands.</param> + /// <returns>List of command verb names.</returns> + private static IEnumerable<string> EnumerateCommandVerbNames(Collection<CmdletInfo> commands) + { + foreach (CmdletInfo command in commands) + { + yield return command.Verb; + } + } + /// <summary> /// Filters verbs by type. /// </summary> @@ -1407,12 +1431,9 @@ private static IEnumerable<VerbInfo> FilterVerbsByType(string[] verbs, Type verb { if (verbs is null || verbs.Length == 0) { - foreach (FieldInfo field in verbType.GetFields()) + foreach (string fieldName in EnumerateFieldNamesFromVerbType(verbType)) { - if (field.IsLiteral) - { - yield return CreateVerbFromField(field, verbType); - } + yield return CreateVerbFromField(fieldName, verbType); } yield break; @@ -1422,17 +1443,14 @@ private static IEnumerable<VerbInfo> FilterVerbsByType(string[] verbs, Type verb verbs, WildcardOptions.IgnoreCase); - foreach (FieldInfo field in verbType.GetFields()) + foreach (string fieldName in EnumerateFieldNamesFromVerbType(verbType)) { - if (field.IsLiteral) - { - if (SessionStateUtilities.MatchesAnyWildcardPattern( - field.Name, + if (SessionStateUtilities.MatchesAnyWildcardPattern( + fieldName, verbPatterns, defaultValue: false)) - { - yield return CreateVerbFromField(field, verbType); - } + { + yield return CreateVerbFromField(fieldName, verbType); } } } @@ -1440,15 +1458,15 @@ private static IEnumerable<VerbInfo> FilterVerbsByType(string[] verbs, Type verb /// <summary> /// Creates Verb info object from field info. /// </summary> - /// <param name="field">The field.</param> + /// <param name="fieldName">The field name.</param> /// <param name="verbType">The verb type.</param> /// <returns>VerbInfo object.</returns> - private static VerbInfo CreateVerbFromField(FieldInfo field, Type verbType) => new() + private static VerbInfo CreateVerbFromField(string fieldName, Type verbType) => new() { - Verb = field.Name, - AliasPrefix = VerbAliasPrefixes.GetVerbAliasPrefix(field.Name), + Verb = fieldName, + AliasPrefix = VerbAliasPrefixes.GetVerbAliasPrefix(fieldName), Group = GetVerbGroupDisplayName(verbType), - Description = VerbDescriptions.GetVerbDescription(field.Name) + Description = VerbDescriptions.GetVerbDescription(fieldName) }; /// <summary> @@ -1472,8 +1490,6 @@ public IEnumerable<CompletionResult> CompleteArgument( CommandAst commandAst, IDictionary fakeBoundParameters) { - var verbs = new string[] { wordToComplete + "*" }; - // Completion: Get-Verb -Group <group> -Verb <wordToComplete> if (commandName.Equals("Get-Verb", StringComparison.OrdinalIgnoreCase) && fakeBoundParameters.Contains("Group")) @@ -1494,12 +1510,7 @@ public IEnumerable<CompletionResult> CompleteArgument( groups = Array.ConvertAll((object[])groupParameterValue, group => group.ToString()); } - foreach (VerbInfo verb in FilterByVerbsAndGroups(verbs, groups)) - { - yield return new CompletionResult(verb.Verb); - } - - yield break; + return CompleteVerbWithGroups(wordToComplete, groups); } // Completion: Get-Command -Noun <noun> -Verb <wordToComplete> @@ -1520,23 +1531,55 @@ public IEnumerable<CompletionResult> CompleteArgument( Collection<CmdletInfo> commands = ps.Invoke<CmdletInfo>(); - foreach (string verb in FilterByVerbsAndCommands(verbs, commands)) - { - yield return new CompletionResult(verb); - } - - yield break; + return CompleteVerbWithCommands(wordToComplete, commands); } // Complete all verbs by default if above cases not completed + return CompleteVerbForAllTypes(wordToComplete); + } + + /// <summary> + /// Completes verb with list of groups. + /// </summary> + /// <param name="wordToComplete">The word to complete.</param> + /// <param name="groups">The list of groups.</param> + /// <returns>List of completions for verb.</returns> + private static IEnumerable<CompletionResult> CompleteVerbWithGroups(string wordToComplete, string[] groups) + { foreach (Type verbType in VerbTypes) { - foreach (VerbInfo verb in FilterVerbsByType(verbs, verbType)) + if (GroupsContainVerbType(groups, verbType)) { - yield return new CompletionResult(verb.Verb); + foreach (CompletionResult result in CompletionHelpers.GetMatchingResults( + wordToComplete, + possibleCompletionValues: EnumerateFieldNamesFromVerbType(verbType))) + { + yield return result; + } } } } + + /// <summary> + /// Completes verb with list of commands. + /// </summary> + /// <param name="wordToComplete">The word to complete.</param> + /// <param name="commands">The list of commands.</param> + /// <returns>List of completions for verb.</returns> + private static IEnumerable<CompletionResult> CompleteVerbWithCommands(string wordToComplete, Collection<CmdletInfo> commands) + => CompletionHelpers.GetMatchingResults( + wordToComplete, + possibleCompletionValues: EnumerateCommandVerbNames(commands)); + + /// <summary> + /// Completes verb for all types. + /// </summary> + /// <param name="wordToComplete">The word to complete.</param> + /// <returns>List of completions for verb.</returns> + private static IEnumerable<CompletionResult> CompleteVerbForAllTypes(string wordToComplete) + => CompletionHelpers.GetMatchingResults( + wordToComplete, + possibleCompletionValues: EnumerateFieldNamesFromAllVerbTypes()); } private static readonly Dictionary<string, bool> s_validVerbs = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase); 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 56db232548a..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>net9.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 769b1b5b275..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>net9.0</TargetFramework> - <LangVersion>11.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 98a949de641..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>11.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 9a546302906..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) { @@ -435,6 +462,27 @@ export $envVarName='$guid' $out = $out.Split([Environment]::NewLine)[0] [System.Management.Automation.Internal.StringDecorated]::new($out).ToString("PlainText") | Should -BeExactly "Exception: boom" } + + It "Progress is not emitted when stdout is redirected" { + $ps = [powershell]::Create() + $null = $ps.AddScript('$a = & ([Environment]::ProcessPath) -Command "Write-Progress -Activity progress"; $a') + $actual = $ps.Invoke() + + $ps.HadErrors | Should -BeFalse + $actual | Should -BeNullOrEmpty + $ps.Streams.Progress | Should -BeNullOrEmpty + } + + It "Progress is still emitted with redireciton with XML output" { + $ps = [powershell]::Create() + $null = $ps.AddScript('$a = & ([Environment]::ProcessPath) -OutputFormat xml -Command "Write-Progress -Activity progress"; $a') + $actual = $ps.Invoke() + + $ps.HadErrors | Should -BeFalse + $actual | Should -BeNullOrEmpty + $ps.Streams.Progress.Count | Should -Be 1 + $ps.Streams.Progress[0].Activity | Should -Be progress + } } Context "Redirected standard output" { @@ -964,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; } @@ -1000,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 @@ -1182,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/HostUtilities.Tests.ps1 b/test/powershell/Host/HostUtilities.Tests.ps1 index da65dc81624..e151a2cfc2b 100644 --- a/test/powershell/Host/HostUtilities.Tests.ps1 +++ b/test/powershell/Host/HostUtilities.Tests.ps1 @@ -81,3 +81,18 @@ Describe 'PromptForCredential' -Tags "CI" { $out.UserName | Should -BeExactly 'myDomain\myUser' } } + +Describe 'PushRunspaceLocalFailure' -Tags 'CI' { + It 'Should throw an exception when pushing a local runspace' { + $runspace = [RunspaceFactory]::CreateRunspace() + try { + $runspace.Open() + $exc = { $Host.PushRunspace($runspace) } | Should -Throw -PassThru + $exc.Exception.InnerException | Should -BeOfType ([System.ArgumentException]) + [string]$exc | Should -BeLike "*PushRunspace can only push a remote runspace. (Parameter 'runspace')*" + } + finally { + $runspace.Dispose() + } + } +} 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 e0ec96aa87b..2280d141ac3 100644 --- a/test/powershell/Host/TabCompletion/BugFix.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/BugFix.Tests.ps1 @@ -43,6 +43,28 @@ Describe "Tab completion bug fix" -Tags "CI" { $result[0].CompletionText | Should -BeExactly '$ErrorActionPreference' } + It "Issue#24756 - Wildcard completions should not return early due to missing results in one container" -Skip:(!$IsWindows) { + try + { + $keys = New-Item -Path @( + 'HKCU:\AB1' + 'HKCU:\AB2' + 'HKCU:\AB2\Test' + ) + + $res = TabExpansion2 -inputScript 'Get-ChildItem -Path HKCU:\AB?\' + $res.CompletionMatches.Count | Should -Be 1 + $res.CompletionMatches[0].CompletionText | Should -BeExactly "HKCU:\AB2\Test" + } + finally + { + if ($keys) + { + Remove-Item -Path HKCU:\AB? -Recurse -ErrorAction SilentlyContinue + } + } + } + Context "Issue#3416 - 'Select-Object'" { BeforeAll { $DatetimeProperties = @((Get-Date).psobject.baseobject.psobject.properties) | Sort-Object -Property Name @@ -104,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 22ff201c45a..f8762a63929 100644 --- a/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 +++ b/test/powershell/Host/TabCompletion/TabCompletion.Tests.ps1 @@ -23,11 +23,77 @@ Describe "TabCompletion" -Tags CI { $res | Should -BeExactly 'Test-AbbreviatedFunctionExpansion' } + It 'Should complete module by shortname' { + $res = TabExpansion2 -inputScript 'Get-Module -ListAvailable -Name Host' + $res.CompletionMatches[0].CompletionText | Should -BeExactly 'Microsoft.PowerShell.Host' + } + It 'Should complete native exe' -Skip:(!$IsWindows) { $res = TabExpansion2 -inputScript 'notep' -cursorColumn 'notep'.Length $res.CompletionMatches[0].CompletionText | Should -BeExactly 'notepad.exe' } + 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 + { + 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 MyTestFunction + $Res.CompletionMatches.Count | Should -Be 2 + $SortedMatches = $Res.CompletionMatches.CompletionText | Sort-Object + $SortedMatches[0] | Should -Be "TestModule1\MyTestFunction" + $SortedMatches[1] | Should -Be "TestModule2\MyTestFunction" + } + 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 + } + } + It 'Should complete dotnet method' { $res = TabExpansion2 -inputScript '(1).ToSt' -cursorColumn '(1).ToSt'.Length $res.CompletionMatches[0].CompletionText | Should -BeExactly 'ToString(' @@ -79,6 +145,17 @@ Describe "TabCompletion" -Tags CI { $res.CompletionMatches[0].CompletionText | Should -BeExactly '$CurrentItem' } + It 'Should complete variables set with an attribute' { + $res = TabExpansion2 -inputScript '[ValidateNotNull()]$Var1 = 1; $Var' + $res.CompletionMatches[0].CompletionText | Should -BeExactly '$Var1' + } + + It 'Should use the first type constraint in a variable assignment in the tooltip' { + $res = TabExpansion2 -inputScript '[int] [string] $Var1 = 1; $Var' + $res.CompletionMatches[0].CompletionText | Should -BeExactly '$Var1' + $res.CompletionMatches[0].ToolTip | Should -BeExactly '[int]$Var1' + } + It 'Should not complete parameter name' { $res = TabExpansion2 -inputScript 'param($P' $res.CompletionMatches.Count | Should -Be 0 @@ -88,6 +165,74 @@ Describe "TabCompletion" -Tags CI { $res = TabExpansion2 -inputScript 'param($PS = $P' $res.CompletionMatches.Count | Should -BeGreaterThan 0 } + + It 'Should complete variable with description and value <Value>' -TestCases @( + @{ Value = 1; Expected = '[int]$VariableWithDescription - Variable description' } + @{ Value = 'string'; Expected = '[string]$VariableWithDescription - Variable description' } + @{ Value = $null; Expected = 'VariableWithDescription - Variable description' } + ) { + param ($Value, $Expected) + + New-Variable -Name VariableWithDescription -Value $Value -Description 'Variable description' -Force + $res = TabExpansion2 -inputScript '$VariableWithDescription' + $res.CompletionMatches.Count | Should -Be 1 + $res.CompletionMatches[0].CompletionText | Should -BeExactly '$VariableWithDescription' + $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' } + @{ Value = $null; Expected = 'VariableWithDescription - Variable description' } + ) { + param ($Value, $Expected) + + New-Variable -Name VariableWithDescription -Value $Value -Description 'Variable description' -Force + $res = TabExpansion2 -inputScript '$local:VariableWithDescription' + $res.CompletionMatches.Count | Should -Be 1 + $res.CompletionMatches[0].CompletionText | Should -BeExactly '$local:VariableWithDescription' + $res.CompletionMatches[0].ToolTip | Should -BeExactly $Expected + } It 'Should not complete property name in class definition' { $res = TabExpansion2 -inputScript 'class X {$P' @@ -102,6 +247,122 @@ Describe "TabCompletion" -Tags CI { } } + context CustomProviderTests { + BeforeAll { + $testModulePath = Join-Path $TestDrive "ReproModule" + New-Item -Path $testModulePath -ItemType Directory > $null + + New-ModuleManifest -Path "$testModulePath/ReproModule.psd1" -RootModule 'testmodule.dll' + + $testBinaryModulePath = Join-Path $testModulePath "testmodule.dll" + $binaryModule = @' +using System; +using System.Linq; +using System.Management.Automation; +using System.Management.Automation.Provider; + +namespace BugRepro +{ + public class IntItemInfo + { + public string Name; + public IntItemInfo(string name) => Name = name; + } + + [CmdletProvider("Int", ProviderCapabilities.None)] + public class IntProvider : NavigationCmdletProvider + { + public static string[] ToChunks(string path) => path.Split("/", StringSplitOptions.RemoveEmptyEntries); + + protected string _ChildName(string path) + { + var name = ToChunks(path).LastOrDefault(); + return name ?? string.Empty; + } + + protected string Normalize(string path) => string.Join("/", ToChunks(path)); + + protected override string GetChildName(string path) + { + var name = _ChildName(path); + // if (!IsItemContainer(path)) { return string.Empty; } + return name; + } + + protected override bool IsValidPath(string path) => int.TryParse(GetChildName(path), out int _); + + protected override bool IsItemContainer(string path) + { + var name = _ChildName(path); + if (!int.TryParse(name, out int value)) + { + return false; + } + if (ToChunks(path).Count() > 3) + { + return false; + } + return value % 2 == 0; + } + + protected override bool ItemExists(string path) + { + foreach (var chunk in ToChunks(path)) + { + if (!int.TryParse(chunk, out int value)) + { + return false; + } + if (value < 0 || value > 9) + { + return false; + } + } + return true; + } + + protected override void GetItem(string path) + { + var name = GetChildName(path); + if (!int.TryParse(name, out int _)) + { + return; + } + WriteItemObject(new IntItemInfo(name), path, IsItemContainer(path)); + } + protected override bool HasChildItems(string path) => IsItemContainer(path); + + protected override void GetChildItems(string path, bool recurse) + { + if (!IsItemContainer(path)) { GetItem(path); return; } + + for (var i = 0; i <= 9; i++) + { + var _path = $"{Normalize(path)}/{i}"; + if (recurse) + { + GetChildItems(_path, recurse); + } + else + { + GetItem(_path); + } + } + } + } +} +'@ + Add-Type -OutputAssembly $testBinaryModulePath -TypeDefinition $binaryModule + + $pwsh = "$PSHOME\pwsh" + } + + It "Should not complete invalid items when a provider path returns itself instead of its children" { + $result = & $pwsh -NoProfile -Command "Import-Module -Name $testModulePath; (TabExpansion2 'Get-ChildItem Int::/2/3/').CompletionMatches.Count" + $result | Should -BeExactly "0" + } + } + It 'should complete index expression for <Intent>' -TestCases @( @{ Intent = 'Hashtable with no user input' @@ -304,6 +565,21 @@ switch ($x) $res.CompletionMatches.Count | Should -Be 0 } + It 'Should complete variable assigned in command redirection to variable' { + $res = TabExpansion2 -inputScript 'New-Guid 1>variable:Redir1 2>variable:Redir2 3>variable:Redir3 4>variable:Redir4 5>variable:Redir5 6>variable:Redir6; $Redir' + $res.CompletionMatches[0].CompletionText | Should -Be '$Redir1' + $res.CompletionMatches[1].CompletionText | Should -Be '$Redir2' + $res.CompletionMatches[1].ToolTip | Should -Be '[ErrorRecord]$Redir2' + $res.CompletionMatches[2].CompletionText | Should -Be '$Redir3' + $res.CompletionMatches[2].ToolTip | Should -Be '[WarningRecord]$Redir3' + $res.CompletionMatches[3].CompletionText | Should -Be '$Redir4' + $res.CompletionMatches[3].ToolTip | Should -Be '[VerboseRecord]$Redir4' + $res.CompletionMatches[4].CompletionText | Should -Be '$Redir5' + $res.CompletionMatches[4].ToolTip | Should -Be '[DebugRecord]$Redir5' + $res.CompletionMatches[5].CompletionText | Should -Be '$Redir6' + $res.CompletionMatches[5].ToolTip | Should -Be '[InformationRecord]$Redir6' + } + context TypeConstructionWithHashtable { BeforeAll { class RandomTestType { @@ -473,6 +749,21 @@ using ` $res.CompletionMatches[0].CompletionText | Should -Be '"Classic"' } + It 'Should work for variable assignment of enum type with type inference' { + $res = TabExpansion2 -inputScript '[System.Management.Automation.ProgressView]$MyUnassignedVar = $psstyle.Progress.View; $MyUnassignedVar = "Class' + $res.CompletionMatches[0].CompletionText | Should -Be '"Classic"' + } + + It 'Should work for property assignment of enum type with type inference with PowerShell class' { + $res = TabExpansion2 -inputScript 'enum Animals{Cat= 0;Dog= 1};class AnimalTestClass{[Animals] $Prop1};$Test1 = [AnimalTestClass]::new();$Test1.Prop1 = "C' + $res.CompletionMatches[0].CompletionText | Should -Be '"Cat"' + } + + It 'Should work for variable assignment with type inference of PowerShell Enum' { + $res = TabExpansion2 -inputScript 'enum Animals{Cat= 0;Dog= 1}; [Animals]$TestVar1 = "D' + $res.CompletionMatches[0].CompletionText | Should -Be '"Dog"' + } + It 'Should work for variable assignment of enum type: <inputStr>' -TestCases @( @{ inputStr = '$ErrorActionPreference = '; filter = ''; doubleQuotes = $false } @{ inputStr = '$ErrorActionPreference='; filter = ''; doubleQuotes = $false } @@ -616,6 +907,11 @@ using ` $res.CompletionMatches.CompletionText | Should -Contain "GetType" } + It 'Should complete variable member inferred from command inside scriptblock' { + $res = TabExpansion2 -inputScript '& {(New-Guid).' + $res.CompletionMatches.Count | Should -BeGreaterThan 0 + } + It 'Should not complete void instance members' { $res = TabExpansion2 -inputScript '([void]("")).' $res.CompletionMatches | Should -BeNullOrEmpty @@ -628,6 +924,20 @@ using ` $completionText -join ' ' | Should -BeExactly 'Equals( new( ReferenceEquals(' } + It 'Should complete variables assigned inside do while loop' { + $TestString = 'do{$Var1 = 1; $Var^ }while ($true)' + $CursorIndex = $TestString.IndexOf('^') + $res = TabExpansion2 -cursorColumn $CursorIndex -inputScript $TestString.Remove($CursorIndex, 1) + $res.CompletionMatches[0].CompletionText | Should -BeExactly '$Var1' + } + + It 'Should complete variables assigned inside do until loop' { + $TestString = 'do{$Var1 = 1; $Var^ }until ($null = Get-ChildItem)' + $CursorIndex = $TestString.IndexOf('^') + $res = TabExpansion2 -cursorColumn $CursorIndex -inputScript $TestString.Remove($CursorIndex, 1) + $res.CompletionMatches[0].CompletionText | Should -BeExactly '$Var1' + } + It 'Should show multiple constructors in the tooltip' { $res = TabExpansion2 -inputScript 'class ConstructorTestClass{ConstructorTestClass ([string] $s){}ConstructorTestClass ([int] $i){}ConstructorTestClass ([int] $i, [bool]$b){}};[ConstructorTestClass]::new' $res.CompletionMatches | Should -HaveCount 1 @@ -661,6 +971,36 @@ ConstructorTestClass(int i, bool b) $diffs | Should -BeNullOrEmpty } + It 'Should complete attribute argument in incomplete param block' { + $res = TabExpansion2 -inputScript 'param([ValidatePattern(' + $Expected = ([ValidatePattern].GetProperties() | Where-Object {$_.CanWrite}).Name -join ',' + $res.CompletionMatches.CompletionText -join ',' | Should -BeExactly $Expected + } + + It 'Should complete attribute argument in incomplete param block on new line' { + $TestString = @' +param([ValidatePattern( +^)]) +'@ + $CursorIndex = $TestString.IndexOf('^') + $res = TabExpansion2 -cursorColumn $CursorIndex -inputScript $TestString.Remove($CursorIndex, 1) + $Expected = ([ValidatePattern].GetProperties() | Where-Object {$_.CanWrite}).Name -join ',' + $res.CompletionMatches.CompletionText -join ',' | Should -BeExactly $Expected + } + + It 'Should complete attribute argument with partially written name in incomplete param block' { + $TestString = 'param([ValidatePattern(op^)]' + $CursorIndex = $TestString.IndexOf('^') + $res = TabExpansion2 -cursorColumn $CursorIndex -inputScript $TestString.Remove($CursorIndex, 1) + $res.CompletionMatches[0].CompletionText | Should -BeExactly 'Options' + } + + It 'Should complete attribute argument for incomplete standalone attribute' { + $res = TabExpansion2 -inputScript '[ValidatePattern(' + $Expected = ([ValidatePattern].GetProperties() | Where-Object {$_.CanWrite}).Name -join ',' + $res.CompletionMatches.CompletionText -join ',' | Should -BeExactly $Expected + } + It 'Should complete argument for second parameter' { $res = TabExpansion2 -inputScript 'Get-ChildItem -Path $HOME -ErrorAction ' $res.CompletionMatches[0].CompletionText | Should -BeExactly Break @@ -672,12 +1012,27 @@ ConstructorTestClass(int i, bool b) $res.CompletionMatches[0].CompletionText | Should -BeExactly Cat } + It 'Should complete cim ETS member added by shortname' -Skip:(!$IsWindows -or (Test-IsWinServer2012R2) -or (Test-IsWindows2016)) { + $res = TabExpansion2 -inputScript '(Get-NetFirewallRule).Nam' + $res.CompletionMatches[0].CompletionText | Should -BeExactly 'Name' + } + It 'Should complete variable assigned with Data statement' { $TestString = 'data MyDataVar {"Hello"};$MyDatav' $res = TabExpansion2 -inputScript $TestString $res.CompletionMatches[0].CompletionText | Should -BeExactly '$MyDataVar' } + It 'Should complete global variable without scope' { + $res = TabExpansion2 -inputScript '$Global:MyTestVar = "Hello";$MyTestV' + $res.CompletionMatches[0].CompletionText | Should -BeExactly '$MyTestVar' + } + + It 'Should complete previously assigned variable in using: scope' { + $res = TabExpansion2 -inputScript '$MyTestVar = "Hello";$Using:MyTestv' + $res.CompletionMatches[0].CompletionText | Should -BeExactly '$Using:MyTestVar' + } + it 'Should complete "Value" parameter value in "Where-Object" for Enum property with no input' { $res = TabExpansion2 -inputScript 'Get-Command | where-Object CommandType -eq ' $res.CompletionMatches[0].CompletionText | Should -BeExactly Alias @@ -759,33 +1114,220 @@ ConstructorTestClass(int i, bool b) $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($path) { - (New-Object -TypeName System.Diagnostics.ProcessStartInfo -ArgumentList $path).Verbs + function GetProcessInfoVerbs([string]$path, [switch]$singleQuote, [switch]$doubleQuote) { + $verbs = (New-Object -TypeName System.Diagnostics.ProcessStartInfo -ArgumentList $path).Verbs + + if ($singleQuote) { + return ($verbs | ForEach-Object { "'$_'" }) + } + elseif ($doubleQuote) { + return ($verbs | ForEach-Object { """$_""" }) + } + + return $verbs } $cmdPath = Join-Path -Path $TestDrive -ChildPath 'test.cmd' - $cmdVerbs = GetProcessInfoVerbs $cmdPath + $cmdVerbs = GetProcessInfoVerbs -Path $cmdPath + $cmdVerbsSingleQuote = GetProcessInfoVerbs -Path $cmdPath -SingleQuote + $cmdVerbsDoubleQuote = GetProcessInfoVerbs -Path $cmdPath -DoubleQuote $exePath = Join-Path -Path $TestDrive -ChildPath 'test.exe' - $exeVerbs = GetProcessInfoVerbs $exePath + $exeVerbs = GetProcessInfoVerbs -Path $exePath $exeVerbsStartingWithRun = $exeVerbs | Where-Object { $_ -like 'run*' } + $exeVerbsSingleQuote = GetProcessInfoVerbs -Path $exePath -SingleQuote + $exeVerbsStartingWithRunSingleQuote = $exeVerbsSingleQuote | Where-Object { $_ -like "'run*" } + $exeVerbsDoubleQuote = GetProcessInfoVerbs -Path $exePath -DoubleQuote + $exeVerbsStartingWithRunDoubleQuote = $exeVerbsDoubleQuote | Where-Object { $_ -like """run*" } $powerShellExeWithNoExtension = 'powershell' $txtPath = Join-Path -Path $TestDrive -ChildPath 'test.txt' - $txtVerbs = GetProcessInfoVerbs $txtPath + $txtVerbs = GetProcessInfoVerbs -Path $txtPath $wavPath = Join-Path -Path $TestDrive -ChildPath 'test.wav' - $wavVerbs = GetProcessInfoVerbs $wavPath + $wavVerbs = GetProcessInfoVerbs -Path $wavPath $docxPath = Join-Path -Path $TestDrive -ChildPath 'test.docx' - $docxVerbs = GetProcessInfoVerbs $docxPath + $docxVerbs = GetProcessInfoVerbs -Path $docxPath $fileWithNoExtensionPath = Join-Path -Path $TestDrive -ChildPath 'test' - $fileWithNoExtensionVerbs = GetProcessInfoVerbs $fileWithNoExtensionPath + $fileWithNoExtensionVerbs = GetProcessInfoVerbs -Path $fileWithNoExtensionPath } It "Should complete Verb parameter for '<TextInput>'" -Skip:(!([System.Management.Automation.Platform]::IsWindowsDesktop)) -TestCases @( @{ TextInput = 'Start-Process -Verb '; ExpectedVerbs = '' } @{ TextInput = "Start-Process -FilePath $cmdPath -Verb "; ExpectedVerbs = $cmdVerbs -join ' ' } + @{ TextInput = "Start-Process -FilePath $cmdPath -Verb '"; ExpectedVerbs = $cmdVerbsSingleQuote -join ' ' } + @{ TextInput = "Start-Process -FilePath $cmdPath -Verb """; ExpectedVerbs = $cmdVerbsDoubleQuote -join ' ' } @{ TextInput = "Start-Process -FilePath $exePath -Verb "; ExpectedVerbs = $exeVerbs -join ' ' } @{ TextInput = "Start-Process -FilePath $exePath -Verb run"; ExpectedVerbs = $exeVerbsStartingWithRun -join ' ' } + @{ TextInput = "Start-Process -FilePath $exePath -Verb 'run"; ExpectedVerbs = $exeVerbsStartingWithRunSingleQuote -join ' ' } + @{ TextInput = "Start-Process -FilePath $exePath -Verb ""run"; ExpectedVerbs = $exeVerbsStartingWithRunDoubleQuote -join ' ' } @{ TextInput = "Start-Process -FilePath $powerShellExeWithNoExtension -Verb "; ExpectedVerbs = $exeVerbs -join ' ' } @{ TextInput = "Start-Process -FilePath $txtPath -Verb "; ExpectedVerbs = $txtVerbs -join ' ' } @{ TextInput = "Start-Process -FilePath $wavPath -Verb "; ExpectedVerbs = $wavVerbs -join ' ' } @@ -802,17 +1344,33 @@ ConstructorTestClass(int i, bool b) Context 'Scope parameter completion' { BeforeAll { $allScopes = 'Global Local Script' + $allScopesSingleQuote = "'Global' 'Local' 'Script'" + $allScopesDoubleQuote = """Global"" ""Local"" ""Script""" $globalScope = 'Global' + $globalScopeSingleQuote = "'Global'" + $globalScopeDoubleQuote = """Global""" $localScope = 'Local' + $localScopeSingleQuote = "'Local'" + $localScopeDoubleQuote = """Local""" $scriptScope = 'Script' + $scriptScopeSingleQuote = "'Script'" + $scriptScopeDoubleQuote = """Script""" $allScopeCommands = 'Clear-Variable', 'Export-Alias', 'Get-Alias', 'Get-PSDrive', 'Get-Variable', 'Import-Alias', 'New-Alias', 'New-PSDrive', 'New-Variable', 'Remove-Alias', 'Remove-PSDrive', 'Remove-Variable', 'Set-Alias', 'Set-Variable' } It "Should complete '<ParameterInput>' for '<Commands>'" -TestCases @( @{ Commands = $allScopeCommands; ParameterInput = "-Scope "; ExpectedScopes = $allScopes } + @{ Commands = $allScopeCommands; ParameterInput = "-Scope '"; ExpectedScopes = $allScopesSingleQuote } + @{ Commands = $allScopeCommands; ParameterInput = "-Scope """; ExpectedScopes = $allScopesDoubleQuote } @{ Commands = $allScopeCommands; ParameterInput = "-Scope G"; ExpectedScopes = $globalScope } + @{ Commands = $allScopeCommands; ParameterInput = "-Scope 'G"; ExpectedScopes = $globalScopeSingleQuote } + @{ Commands = $allScopeCommands; ParameterInput = "-Scope ""G"; ExpectedScopes = $globalScopeDoubleQuote } @{ Commands = $allScopeCommands; ParameterInput = "-Scope Lo"; ExpectedScopes = $localScope } + @{ Commands = $allScopeCommands; ParameterInput = "-Scope 'Lo"; ExpectedScopes = $localScopeSingleQuote } + @{ Commands = $allScopeCommands; ParameterInput = "-Scope ""Lo"; ExpectedScopes = $localScopeDoubleQuote } @{ Commands = $allScopeCommands; ParameterInput = "-Scope Scr"; ExpectedScopes = $scriptScope } + @{ Commands = $allScopeCommands; ParameterInput = "-Scope 'Scr"; ExpectedScopes = $scriptScopeSingleQuote } + @{ Commands = $allScopeCommands; ParameterInput = "-Scope ""Scr"; ExpectedScopes = $scriptScopeDoubleQuote } @{ Commands = $allScopeCommands; ParameterInput = "-Scope NonExistentScope"; ExpectedScopes = '' } ) { param($Commands, $ParameterInput, $ExpectedScopes) @@ -828,28 +1386,42 @@ ConstructorTestClass(int i, bool b) Context 'Get-Verb & Get-Command -Verb parameter completion' { BeforeAll { $allVerbs = 'Add Approve Assert Backup Block Build Checkpoint Clear Close Compare Complete Compress Confirm Connect Convert ConvertFrom ConvertTo Copy Debug Deny Deploy Disable Disconnect Dismount Edit Enable Enter Exit Expand Export Find Format Get Grant Group Hide Import Initialize Install Invoke Join Limit Lock Measure Merge Mount Move New Open Optimize Out Ping Pop Protect Publish Push Read Receive Redo Register Remove Rename Repair Request Reset Resize Resolve Restart Restore Resume Revoke Save Search Select Send Set Show Skip Split Start Step Stop Submit Suspend Switch Sync Test Trace Unblock Undo Uninstall Unlock Unprotect Unpublish Unregister Update Use Wait Watch Write' + $allVerbsSingleQuote = "'Add' 'Approve' 'Assert' 'Backup' 'Block' 'Build' 'Checkpoint' 'Clear' 'Close' 'Compare' 'Complete' 'Compress' 'Confirm' 'Connect' 'Convert' 'ConvertFrom' 'ConvertTo' 'Copy' 'Debug' 'Deny' 'Deploy' 'Disable' 'Disconnect' 'Dismount' 'Edit' 'Enable' 'Enter' 'Exit' 'Expand' 'Export' 'Find' 'Format' 'Get' 'Grant' 'Group' 'Hide' 'Import' 'Initialize' 'Install' 'Invoke' 'Join' 'Limit' 'Lock' 'Measure' 'Merge' 'Mount' 'Move' 'New' 'Open' 'Optimize' 'Out' 'Ping' 'Pop' 'Protect' 'Publish' 'Push' 'Read' 'Receive' 'Redo' 'Register' 'Remove' 'Rename' 'Repair' 'Request' 'Reset' 'Resize' 'Resolve' 'Restart' 'Restore' 'Resume' 'Revoke' 'Save' 'Search' 'Select' 'Send' 'Set' 'Show' 'Skip' 'Split' 'Start' 'Step' 'Stop' 'Submit' 'Suspend' 'Switch' 'Sync' 'Test' 'Trace' 'Unblock' 'Undo' 'Uninstall' 'Unlock' 'Unprotect' 'Unpublish' 'Unregister' 'Update' 'Use' 'Wait' 'Watch' 'Write'" + $allVerbsDoubleQuote = """Add"" ""Approve"" ""Assert"" ""Backup"" ""Block"" ""Build"" ""Checkpoint"" ""Clear"" ""Close"" ""Compare"" ""Complete"" ""Compress"" ""Confirm"" ""Connect"" ""Convert"" ""ConvertFrom"" ""ConvertTo"" ""Copy"" ""Debug"" ""Deny"" ""Deploy"" ""Disable"" ""Disconnect"" ""Dismount"" ""Edit"" ""Enable"" ""Enter"" ""Exit"" ""Expand"" ""Export"" ""Find"" ""Format"" ""Get"" ""Grant"" ""Group"" ""Hide"" ""Import"" ""Initialize"" ""Install"" ""Invoke"" ""Join"" ""Limit"" ""Lock"" ""Measure"" ""Merge"" ""Mount"" ""Move"" ""New"" ""Open"" ""Optimize"" ""Out"" ""Ping"" ""Pop"" ""Protect"" ""Publish"" ""Push"" ""Read"" ""Receive"" ""Redo"" ""Register"" ""Remove"" ""Rename"" ""Repair"" ""Request"" ""Reset"" ""Resize"" ""Resolve"" ""Restart"" ""Restore"" ""Resume"" ""Revoke"" ""Save"" ""Search"" ""Select"" ""Send"" ""Set"" ""Show"" ""Skip"" ""Split"" ""Start"" ""Step"" ""Stop"" ""Submit"" ""Suspend"" ""Switch"" ""Sync"" ""Test"" ""Trace"" ""Unblock"" ""Undo"" ""Uninstall"" ""Unlock"" ""Unprotect"" ""Unpublish"" ""Unregister"" ""Update"" ""Use"" ""Wait"" ""Watch"" ""Write""" $verbsStartingWithRe = 'Read Receive Redo Register Remove Rename Repair Request Reset Resize Resolve Restart Restore Resume Revoke' $verbsStartingWithEx = 'Exit Expand Export' $verbsStartingWithConv = 'Convert ConvertFrom ConvertTo' $lifeCycleVerbsStartingWithRe = 'Register Request Restart Resume' + $lifeCycleVerbsStartingWithReSingleQuote = "'Register' 'Request' 'Restart' 'Resume'" + $lifeCycleVerbsStartingWithReDoubleQuote = """Register"" ""Request"" ""Restart"" ""Resume""" $dataVerbsStartingwithEx = 'Expand Export' $lifeCycleAndCommmonVerbsStartingWithRe = 'Redo Register Remove Rename Request Reset Resize Restart Resume' $allLifeCycleAndCommonVerbs = 'Add Approve Assert Build Clear Close Complete Confirm Copy Deny Deploy Disable Enable Enter Exit Find Format Get Hide Install Invoke Join Lock Move New Open Optimize Pop Push Redo Register Remove Rename Request Reset Resize Restart Resume Search Select Set Show Skip Split Start Step Stop Submit Suspend Switch Undo Uninstall Unlock Unregister Wait Watch' $allJsonVerbs = 'ConvertFrom ConvertTo Test' $jsonVerbsStartingWithConv = 'ConvertFrom ConvertTo' + $jsonVerbsStartingWithConvSingleQuote = "'ConvertFrom' 'ConvertTo'" + $jsonVerbsStartingWithConvDoubleQuote = """ConvertFrom"" ""ConvertTo""" $allJsonAndJobVerbs = 'ConvertFrom ConvertTo Debug Get Receive Remove Start Stop Test Wait' $jsonAndJobVerbsStartingWithSt = 'Start Stop' $allObjectVerbs = 'Compare ForEach Group Measure New Select Sort Tee Where' $utilityModuleObjectVerbs = 'Compare Group Measure New Select Sort Tee' $utilityModuleObjectVerbsStartingWithS = 'Select Sort' + $utilityModuleObjectVerbsStartingWithSSingleQuote = "'Select' 'Sort'" + $utilityModuleObjectVerbsStartingWithSDoubleQuote = """Select"" ""Sort""" + $utilityModuleObjectVerbsStartingWithS $coreModuleObjectVerbs = 'ForEach Where' } It "Should complete Verb parameter for '<TextInput>'" -TestCases @( @{ TextInput = 'Get-Verb -Verb '; ExpectedVerbs = $allVerbs } + @{ TextInput = "Get-Verb -Verb '"; ExpectedVerbs = $allVerbsSingleQuote } + @{ TextInput = "Get-Verb -Verb """; ExpectedVerbs = $allVerbsDoubleQuote } @{ TextInput = 'Get-Verb -Group Lifecycle, Common -Verb '; ExpectedVerbs = $allLifeCycleAndCommonVerbs } @{ TextInput = 'Get-Verb -Verb Re'; ExpectedVerbs = $verbsStartingWithRe } @{ TextInput = 'Get-Verb -Group Lifecycle -Verb Re'; ExpectedVerbs = $lifeCycleVerbsStartingWithRe } + @{ TextInput = "Get-Verb -Group Lifecycle -Verb 'Re"; ExpectedVerbs = $lifeCycleVerbsStartingWithReSingleQuote } + @{ TextInput = "Get-Verb -Group Lifecycle -Verb ""Re"; ExpectedVerbs = $lifeCycleVerbsStartingWithReDoubleQuote } + @{ TextInput = 'Get-Verb -Group Lifecycle -Verb Re'; ExpectedVerbs = $lifeCycleVerbsStartingWithRe } @{ TextInput = 'Get-Verb -Group Lifecycle, Common -Verb Re'; ExpectedVerbs = $lifeCycleAndCommmonVerbsStartingWithRe } @{ TextInput = 'Get-Verb -Verb Ex'; ExpectedVerbs = $verbsStartingWithEx } @{ TextInput = 'Get-Verb -Group Data -Verb Ex'; ExpectedVerbs = $dataVerbsStartingwithEx } @@ -861,12 +1433,16 @@ ConstructorTestClass(int i, bool b) @{ TextInput = 'Get-Command -Verb Conv'; ExpectedVerbs = $verbsStartingWithConv } @{ TextInput = 'Get-Command -Noun Json -Verb '; ExpectedVerbs = $allJsonVerbs } @{ TextInput = 'Get-Command -Noun Json -Verb Conv'; ExpectedVerbs = $jsonVerbsStartingWithConv } + @{ TextInput = "Get-Command -Noun Json -Verb 'Conv"; ExpectedVerbs = $jsonVerbsStartingWithConvSingleQuote } + @{ TextInput = "Get-Command -Noun Json -Verb ""Conv"; ExpectedVerbs = $jsonVerbsStartingWithConvDoubleQuote } @{ TextInput = 'Get-Command -Noun Json, Job -Verb '; ExpectedVerbs = $allJsonAndJobVerbs } @{ TextInput = 'Get-Command -Noun Json, Job -Verb St'; ExpectedVerbs = $jsonAndJobVerbsStartingWithSt } @{ TextInput = 'Get-Command -Noun NonExistentNoun -Verb '; ExpectedVerbs = '' } @{ TextInput = 'Get-Command -Noun Object -Module Microsoft.PowerShell.Utility,Microsoft.PowerShell.Core -Verb '; ExpectedVerbs = $allObjectVerbs } @{ TextInput = 'Get-Command -Noun Object -Module Microsoft.PowerShell.Utility -Verb '; ExpectedVerbs = $utilityModuleObjectVerbs } @{ TextInput = 'Get-Command -Noun Object -Module Microsoft.PowerShell.Utility -Verb S'; ExpectedVerbs = $utilityModuleObjectVerbsStartingWithS } + @{ TextInput = "Get-Command -Noun Object -Module Microsoft.PowerShell.Utility -Verb 'S"; ExpectedVerbs = $utilityModuleObjectVerbsStartingWithSSingleQuote } + @{ TextInput = "Get-Command -Noun Object -Module Microsoft.PowerShell.Utility -Verb ""S"; ExpectedVerbs = $utilityModuleObjectVerbsStartingWithSDoubleQuote } @{ TextInput = 'Get-Command -Noun Object -Module Microsoft.PowerShell.Core -Verb '; ExpectedVerbs = $coreModuleObjectVerbs } ) { param($TextInput, $ExpectedVerbs) @@ -879,18 +1455,26 @@ ConstructorTestClass(int i, bool b) Context 'StrictMode Version parameter completion' { BeforeAll { $allStrictModeVersions = '1.0 2.0 3.0 Latest' + $allStrictModeVersionsSingleQuote = "'1.0' '2.0' '3.0' 'Latest'" + $allStrictModeVersionsDoubleQuote = """1.0"" ""2.0"" ""3.0"" ""Latest""" $versionOne = '1.0' $versionTwo = '2.0' $versionThree = '3.0' $latestVersion = 'Latest' + $latestVersionSingleQuote = "'Latest'" + $latestVersionDoubleQuote = """Latest""" } It "Should complete Version for '<TextInput>'" -TestCases @( @{ TextInput = "Set-StrictMode -Version "; ExpectedVersions = $allStrictModeVersions } + @{ TextInput = "Set-StrictMode -Version '"; ExpectedVersions = $allStrictModeVersionsSingleQuote } + @{ TextInput = "Set-StrictMode -Version """; ExpectedVersions = $allStrictModeVersionsDoubleQuote } @{ TextInput = "Set-StrictMode -Version 1"; ExpectedVersions = $versionOne } @{ TextInput = "Set-StrictMode -Version 2"; ExpectedVersions = $versionTwo } @{ TextInput = "Set-StrictMode -Version 3"; ExpectedVersions = $versionThree } @{ TextInput = "Set-StrictMode -Version Lat"; ExpectedVersions = $latestVersion } + @{ TextInput = "Set-StrictMode -Version 'Lat"; ExpectedVersions = $latestVersionSingleQuote } + @{ TextInput = "Set-StrictMode -Version ""Lat"; ExpectedVersions = $latestVersionDoubleQuote } @{ TextInput = "Set-StrictMode -Version NonExistentVersion"; ExpectedVersions = '' } ) { param($TextInput, $ExpectedVersions) @@ -926,6 +1510,318 @@ ConstructorTestClass(int i, bool b) } } + Context 'New-ItemProperty -PropertyType parameter completion' { + BeforeAll { + if ($IsWindows) { + $allRegistryValueKinds = 'String ExpandString Binary DWord MultiString QWord Unknown' + $allRegistryValueKindsWithQuotes = "'String' 'ExpandString' 'Binary' 'DWord' 'MultiString' 'QWord' 'Unknown'" + $dwordValueKind = 'DWord' + $qwordValueKind = 'QWord' + $binaryValueKind = 'Binary' + $multiStringValueKind = 'MultiString' + $registryPath = "HKCU:\test1\sub" + New-Item -Path $registryPath -Force + $registryLiteralPath = "HKCU:\test2\*\sub" + New-Item -Path $registryLiteralPath -Force + $fileSystemPath = "TestDrive:\test1.txt" + New-Item -Path $fileSystemPath -Force + $fileSystemLiteralPathDir = "TestDrive:\[]" + $fileSystemLiteralPath = "$fileSystemLiteralPathDir\test2.txt" + New-Item -Path $fileSystemLiteralPath -Force + } + } + + It "Should complete Property Type for '<TextInput>'" -Skip:(!$IsWindows) -TestCases @( + # -Path completions + @{ TextInput = "New-ItemProperty -Path $registryPath -PropertyType "; ExpectedPropertyTypes = $allRegistryValueKinds } + @{ TextInput = "New-ItemProperty -Path $registryPath -PropertyType d"; ExpectedPropertyTypes = $dwordValueKind } + @{ TextInput = "New-ItemProperty -Path $registryPath -PropertyType q"; ExpectedPropertyTypes = $qwordValueKind } + @{ TextInput = "New-ItemProperty -Path $registryPath -PropertyType bin"; ExpectedPropertyTypes = $binaryValueKind } + @{ TextInput = "New-ItemProperty -Path $registryPath -PropertyType multi"; ExpectedPropertyTypes = $multiStringValueKind } + @{ TextInput = "New-ItemProperty -Path $registryPath -PropertyType invalidproptype"; ExpectedPropertyTypes = '' } + @{ TextInput = "New-ItemProperty -Path $fileSystemPath -PropertyType "; ExpectedPropertyTypes = '' } + + # -LiteralPath completions + @{ TextInput = "New-ItemProperty -LiteralPath $registryLiteralPath -PropertyType "; ExpectedPropertyTypes = $allRegistryValueKinds } + @{ TextInput = "New-ItemProperty -LiteralPath $registryLiteralPath -PropertyType d"; ExpectedPropertyTypes = $dwordValueKind } + @{ TextInput = "New-ItemProperty -LiteralPath $registryLiteralPath -PropertyType q"; ExpectedPropertyTypes = $qwordValueKind } + @{ TextInput = "New-ItemProperty -LiteralPath $registryLiteralPath -PropertyType bin"; ExpectedPropertyTypes = $binaryValueKind } + @{ TextInput = "New-ItemProperty -LiteralPath $registryLiteralPath -PropertyType multi"; ExpectedPropertyTypes = $multiStringValueKind } + @{ TextInput = "New-ItemProperty -LiteralPath $registryLiteralPath -PropertyType invalidproptype"; ExpectedPropertyTypes = '' } + @{ TextInput = "New-ItemProperty -LiteralPath $fileSystemLiteralPath -PropertyType "; ExpectedPropertyTypes = '' } + + # All of these should return no completion since they don't specify -Path/-LiteralPath + @{ TextInput = "New-ItemProperty -PropertyType "; ExpectedPropertyTypes = '' } + @{ TextInput = "New-ItemProperty -PropertyType d"; ExpectedPropertyTypes = '' } + @{ TextInput = "New-ItemProperty -PropertyType q"; ExpectedPropertyTypes = '' } + @{ TextInput = "New-ItemProperty -PropertyType bin"; ExpectedPropertyTypes = '' } + @{ TextInput = "New-ItemProperty -PropertyType multi"; ExpectedPropertyTypes = '' } + @{ TextInput = "New-ItemProperty -PropertyType invalidproptype"; ExpectedPropertyTypes = '' } + + # All of these should return completion even with quotes included + @{ TextInput = "New-ItemProperty -Path $registryPath -PropertyType '"; ExpectedPropertyTypes = $allRegistryValueKindsWithQuotes } + @{ TextInput = "New-ItemProperty -Path $registryPath -PropertyType 'bin"; ExpectedPropertyTypes = "'$binaryValueKind'" } + ) { + param($TextInput, $ExpectedPropertyTypes) + $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) { + try { + Push-Location HKCU:\ + $textInput = "New-ItemProperty -PropertyType " + $res = TabExpansion2 -inputScript $textInput -cursorColumn $textInput.Length + $completionText = $res.CompletionMatches.CompletionText + $completionText -join ' ' | Should -BeExactly $allRegistryValueKinds + } + finally { + Pop-Location + } + } + + AfterAll { + if ($IsWindows) { + Remove-Item -Path $registryPath -Force + Remove-Item -LiteralPath $registryLiteralPath -Force + Remove-Item -Path $fileSystemPath -Force + Remove-Item -LiteralPath $fileSystemLiteralPathDir -Recurse -Force + } + } + } + + Context 'Get-Command -Noun parameter completion' { + BeforeAll { + function GetModuleCommandNouns( + [string]$Module, + [string]$Verb, + [switch]$SingleQuote, + [switch]$DoubleQuote) + { + + $commandParams = @{} + + if ($PSBoundParameters.ContainsKey('Module')) { + $commandParams['Module'] = $Module + } + + if ($PSBoundParameters.ContainsKey('Verb')) { + $commandParams['Verb'] = $Verb + } + + $nouns = (Get-Command @commandParams).Noun + + if ($SingleQuote) { + return ($nouns | ForEach-Object { "'$_'" }) + } + elseif ($DoubleQuote) { + return ($nouns | ForEach-Object { """$_""" }) + } + + return $nouns + } + + $utilityModuleName = 'Microsoft.PowerShell.Utility' + + $allUtilityCommandNouns = GetModuleCommandNouns -Module $utilityModuleName + $allUtilityCommandNounsSingleQuote = GetModuleCommandNouns -Module $utilityModuleName -SingleQuote + $allUtilityCommandNounsDoubleQuote = GetModuleCommandNouns -Module $utilityModuleName -DoubleQuote + $utilityCommandNounsStartingWithF = $allUtilityCommandNouns | Where-Object { $_ -like 'F*'} + $utilityCommandNounsStartingWithFSingleQuote = $allUtilityCommandNounsSingleQuote | Where-Object { $_ -like "'F*"} + $utilityCommandNounsStartingWithFDoubleQuote = $allUtilityCommandNounsDoubleQuote | Where-Object { $_ -like """F*"} + + $allUtilityCommandNounsWithConvertToVerb = GetModuleCommandNouns -Module $utilityModuleName -Verb 'ConvertTo' + $allUtilityCommandNounsWithConvertToVerbSingleQuote = GetModuleCommandNouns -Module $utilityModuleName -SingleQuote -Verb 'ConvertTo' + $allUtilityCommandNounsWithConvertToVerbDoubleQuote = GetModuleCommandNouns -Module $utilityModuleName -DoubleQuote -Verb 'ConvertTo' + $utilityCommandNounsWithConvertToVerb = $allUtilityCommandNounsWithConvertToVerb | Where-Object { $_ -in 'CliXml', 'Csv', 'Html', 'Json', 'Xml' } + $utilityCommandNounsWithConvertToVerbSingleQuote = $allUtilityCommandNounsWithConvertToVerbSingleQuote | Where-Object { $_ -in "'CliXml'", "'Csv'", "'Html'", "'Json'", "'Xml'" } + $utilityCommandNounsWithConvertToVerbDoubleQuote = $allUtilityCommandNounsWithConvertToVerbDoubleQuote | Where-Object { $_ -in """CliXml""", """Csv""", """Html""", """Json""", """Xml""" } + $utilityCommandNounsWithConvertToVerbStartingWithC = $allUtilityCommandNounsWithConvertToVerb | Where-Object { $_ -in 'CliXml', 'Csv' } + $utilityCommandNounsWithConvertToVerbStartingWithCSingleQuote = $allUtilityCommandNounsWithConvertToVerbSingleQuote | Where-Object { $_ -in "'CliXml'", "'Csv'" } + $utilityCommandNounsWithConvertToVerbStartingWithCDoubleQuote = $allUtilityCommandNounsWithConvertToVerbDoubleQuote | Where-Object { $_ -in """CliXml""", """Csv""" } + } + + It "Should complete Noun for '<TextInput>'" -TestCases @( + @{ TextInput = "Get-Command -Module $utilityModuleName -Noun "; ExpectedNouns = $allUtilityCommandNouns } + @{ TextInput = "Get-Command -Module $utilityModuleName -Noun '"; ExpectedNouns = $allUtilityCommandNounsSingleQuote } + @{ TextInput = "Get-Command -Module $utilityModuleName -Noun """; ExpectedNouns = $allUtilityCommandNounsDoubleQuote } + @{ TextInput = "Get-Command -Module $utilityModuleName -Noun F"; ExpectedNouns = $utilityCommandNounsStartingWithF } + @{ TextInput = "Get-Command -Module $utilityModuleName -Noun 'F"; ExpectedNouns = $utilityCommandNounsStartingWithFSingleQuote } + @{ TextInput = "Get-Command -Module $utilityModuleName -Noun ""F"; ExpectedNouns = $utilityCommandNounsStartingWithFDoubleQuote } + @{ TextInput = "Get-Command -Module $utilityModuleName -Verb ConvertTo -Noun "; ExpectedNouns = $utilityCommandNounsWithConvertToVerb } + @{ TextInput = "Get-Command -Module $utilityModuleName -Verb ConvertTo -Noun '"; ExpectedNouns = $utilityCommandNounsWithConvertToVerbSingleQuote } + @{ TextInput = "Get-Command -Module $utilityModuleName -Verb ConvertTo -Noun """; ExpectedNouns = $utilityCommandNounsWithConvertToVerbDoubleQuote } + @{ TextInput = "Get-Command -Module $utilityModuleName -Verb ConvertTo -Noun C"; ExpectedNouns = $utilityCommandNounsWithConvertToVerbStartingWithC } + @{ TextInput = "Get-Command -Module $utilityModuleName -Verb ConvertTo -Noun 'C"; ExpectedNouns = $utilityCommandNounsWithConvertToVerbStartingWithCSingleQuote } + @{ TextInput = "Get-Command -Module $utilityModuleName -Verb ConvertTo -Noun ""C"; ExpectedNouns = $utilityCommandNounsWithConvertToVerbStartingWithCDoubleQuote } + ) { + param($TextInput, $ExpectedNouns) + $res = TabExpansion2 -inputScript $TextInput -cursorColumn $TextInput.Length + $completionText = $res.CompletionMatches.CompletionText + + # Avoid using Sort-Object -Unique because it generates different order than SortedSet on MacOS/Linux + $sortedSetExpectedNouns = [System.Collections.Generic.SortedSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) + foreach ($noun in $ExpectedNouns) + { + $sortedSetExpectedNouns.Add($noun) | Out-Null + } + + $completionText -join ' ' | Should -BeExactly ($sortedSetExpectedNouns -join ' ') + } + } + + Context "Get-ExperimentalFeature -Name parameter completion" { + BeforeAll { + function GetExperimentalFeatureNames([switch]$SingleQuote, [switch]$DoubleQuote) { + $features = (Get-ExperimentalFeature).Name + + if ($SingleQuote) { + return ($features | ForEach-Object { "'$_'" }) + } + elseif ($DoubleQuote) { + return ($features | ForEach-Object { """$_""" }) + } + + return $features + } + + $allExperimentalFeatures = GetExperimentalFeatureNames + $allExperimentalFeaturesSingleQuote = GetExperimentalFeatureNames -SingleQuote + $allExperimentalFeaturesDoubleQuote = GetExperimentalFeatureNames -DoubleQuote + $experimentalFeaturesStartingWithPS = $allExperimentalFeatures | Where-Object { $_ -like 'PS*'} + $experimentalFeaturesStartingWithPSSingleQuote = $allExperimentalFeaturesSingleQuote | Where-Object { $_ -like "'PS*" } + $experimentalFeaturesStartingWithPSDoubleQuote = $allExperimentalFeaturesDoubleQuote | Where-Object { $_ -like """PS*" } + } + + It "Should complete Name for '<TextInput>'" -TestCases @( + @{ TextInput = "Get-ExperimentalFeature -Name "; ExpectedExperimentalFeatureNames = $allExperimentalFeatures } + @{ TextInput = "Get-ExperimentalFeature -Name '"; ExpectedExperimentalFeatureNames = $allExperimentalFeaturesSingleQuote } + @{ TextInput = "Get-ExperimentalFeature -Name """; ExpectedExperimentalFeatureNames = $allExperimentalFeaturesDoubleQuote } + @{ TextInput = "Get-ExperimentalFeature -Name PS"; ExpectedExperimentalFeatureNames = $experimentalFeaturesStartingWithPS } + @{ TextInput = "Get-ExperimentalFeature -Name 'PS"; ExpectedExperimentalFeatureNames = $experimentalFeaturesStartingWithPSSingleQuote } + @{ TextInput = "Get-ExperimentalFeature -Name ""PS"; ExpectedExperimentalFeatureNames = $experimentalFeaturesStartingWithPSDoubleQuote } + ) { + param($TextInput, $ExpectedExperimentalFeatureNames) + $res = TabExpansion2 -inputScript $TextInput -cursorColumn $TextInput.Length + $completionText = $res.CompletionMatches.CompletionText + $completionText -join ' ' | Should -BeExactly (($ExpectedExperimentalFeatureNames | Sort-Object -Unique) -join ' ') + } + } + + 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 = @' @@ -1033,8 +1929,12 @@ ConstructorTestClass(int i, bool b) 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) @@ -1098,6 +1998,52 @@ ConstructorTestClass(int i, bool b) $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 { @@ -1238,6 +2184,36 @@ class InheritedClassTest : System.Attribute } } + Context "Script parameter completion" { + BeforeAll { + Setup -File -Path 'ModuleReqTest.ps1' -Content @' +#requires -Modules ThisModuleDoesNotExist +param ($Param1) +'@ + Setup -File -Path 'AdminReqTest.ps1' -Content @' +#requires -RunAsAdministrator +param ($Param1) +'@ + Push-Location ${TestDrive}\ + } + + AfterAll { + Pop-Location + } + + It "Input should successfully complete script parameter for script with failed script requirements" { + $res = TabExpansion2 -inputScript '.\ModuleReqTest.ps1 -' + $res.CompletionMatches.Count | Should -BeGreaterThan 0 + $res.CompletionMatches[0].CompletionText | Should -BeExactly '-Param1' + } + + It "Input should successfully complete script parameter for admin script while not elevated" { + $res = TabExpansion2 -inputScript '.\AdminReqTest.ps1 -' + $res.CompletionMatches.Count | Should -BeGreaterThan 0 + $res.CompletionMatches[0].CompletionText | Should -BeExactly '-Param1' + } + } + Context "File name completion" { BeforeAll { $tempDir = Join-Path -Path $TestDrive -ChildPath "baseDir" @@ -1409,6 +2385,34 @@ class InheritedClassTest : System.Attribute $res.CompletionMatches[0].CompletionText | Should -Be "`"$expectedPath`"" } + It "Relative path completion for using <UsingKind> statement when AST extent has file identity" -TestCases @( + @{UsingKind = "module"; ExpectedFileName = 'UsingFileCompletionModuleTest.psm1'} + @{UsingKind = "assembly";ExpectedFileName = 'UsingFileCompletionAssemblyTest.dll'} + ) -test { + param($UsingKind, $ExpectedFileName) + $scriptText = "using $UsingKind .\UsingFileCompletion" + $tokens = $null + $scriptAst = [System.Management.Automation.Language.Parser]::ParseInput( + $scriptText, + (Join-Path -Path $tempDir -ChildPath ScriptInEditor.ps1), + [ref] $tokens, + [ref] $null) + + $cursorPosition = $scriptAst.Extent.StartScriptPosition. + GetType(). + GetMethod('CloneWithNewOffset', [System.Reflection.BindingFlags]'NonPublic, Instance'). + Invoke($scriptAst.Extent.StartScriptPosition, @($scriptText.Length - 1)) + + Push-Location -LiteralPath $PSHOME + $TestFile = Join-Path -Path $tempDir -ChildPath $ExpectedFileName + $null = New-Item -Path $TestFile + $res = TabExpansion2 -ast $scriptAst -tokens $tokens -positionOfCursor $cursorPosition + Pop-Location + + $ExpectedPath = Join-Path -Path '.\' -ChildPath $ExpectedFileName + $res.CompletionMatches.CompletionText | Where-Object {$_ -Like "*$ExpectedFileName"} | Should -Be $ExpectedPath + } + It "Should handle '~' in completiontext when it's used to refer to home in input" { $res = TabExpansion2 -inputScript "~$separator" # select the first answer which does not have a space in the completion (those completions look like & '3D Objects') @@ -1460,6 +2464,42 @@ class InheritedClassTest : System.Attribute 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) { @@ -1583,6 +2623,7 @@ class InheritedClassTest : System.Attribute @{ inputStr = 'gmo Microsoft.PowerShell.U'; expected = 'Microsoft.PowerShell.Utility'; setup = $null } @{ inputStr = 'rmo Microsoft.PowerShell.U'; expected = 'Microsoft.PowerShell.Utility'; setup = $null } @{ inputStr = 'gcm -Module Microsoft.PowerShell.U'; expected = 'Microsoft.PowerShell.Utility'; setup = $null } + @{ inputStr = 'gcm -ExcludeModule Microsoft.PowerShell.U'; expected = 'Microsoft.PowerShell.Utility'; setup = $null } @{ inputStr = 'gmo -list PackageM'; expected = 'PackageManagement'; setup = $null } @{ inputStr = 'gcm -Module PackageManagement Find-Pac'; expected = 'Find-Package'; setup = $null } @{ inputStr = 'ipmo PackageM'; expected = 'PackageManagement'; setup = $null } @@ -1649,6 +2690,10 @@ class InheritedClassTest : System.Attribute $res.CompletionMatches[0].CompletionText | Should -BeExactly $afterTab } + It "Tab completion UNC path with filesystem provider" -Skip:(!$IsWindows) { + $res = TabExpansion2 -inputScript 'Filesystem::\\localhost\admin' + $res.CompletionMatches[0].CompletionText | Should -BeExactly 'Filesystem::\\localhost\ADMIN$' + } It "Tab completion for registry" -Skip:(!$IsWindows) { $beforeTab = 'registry::HKEY_l' @@ -1949,7 +2994,7 @@ class InheritedClassTest : System.Attribute } It "Test hashtable key completion in #requires statement for modules" { - $res = TabExpansion2 -inputScript "#requires -Modules @{" -cursorColumn 21 + $res = TabExpansion2 -inputScript "#requires -Modules @{" $res.CompletionMatches.Count | Should -BeGreaterThan 0 $res.CompletionMatches[0].CompletionText | Should -BeExactly "GUID" } @@ -2500,15 +3545,18 @@ dir -Recurse ` Context "Module cmdlet completion tests" { It "ArugmentCompleter for PSEdition should work for '<cmd>'" -TestCases @( @{cmd = "Get-Module -PSEdition "; expected = "Desktop", "Core"} + @{cmd = "Get-Module -PSEdition '"; expected = "'Desktop'", "'Core'"} + @{cmd = "Get-Module -PSEdition """; expected = """Desktop""", """Core"""} + @{cmd = "Get-Module -PSEdition 'Desk"; expected = "'Desktop'"} + @{cmd = "Get-Module -PSEdition ""Desk"; expected = """Desktop"""} + @{cmd = "Get-Module -PSEdition Co"; expected = "Core"} + @{cmd = "Get-Module -PSEdition 'Co"; expected = "'Core'"} + @{cmd = "Get-Module -PSEdition ""Co"; expected = """Core"""} ) { param($cmd, $expected) $res = TabExpansion2 -inputScript $cmd -cursorColumn $cmd.Length - $res.CompletionMatches | Should -HaveCount $expected.Count - $completionOptions = "" - foreach ($completion in $res.CompletionMatches) { - $completionOptions += $completion.ListItemText - } - $completionOptions | Should -BeExactly ([string]::Join("", $expected)) + $completionText = $res.CompletionMatches.CompletionText + $completionText -join ' ' | Should -BeExactly ($expected -join ' ') } } @@ -2615,23 +3663,23 @@ dir -Recurse ` } It '<Intent>' -TestCases @( @{ - Intent = 'Complete help keywords with minimum input' + Intent = 'Complete help keywords with minimal input' Expected = @( - 'COMPONENT' - 'DESCRIPTION' - 'EXAMPLE' - 'EXTERNALHELP' - 'FORWARDHELPCATEGORY' - 'FORWARDHELPTARGETNAME' - 'FUNCTIONALITY' - 'INPUTS' - 'LINK' - 'NOTES' - 'OUTPUTS' - 'PARAMETER' - 'REMOTEHELPRUNSPACE' - 'ROLE' - 'SYNOPSIS' + "COMPONENT", + "DESCRIPTION", + "EXAMPLE", + "EXTERNALHELP", + "FORWARDHELPCATEGORY", + "FORWARDHELPTARGETNAME", + "FUNCTIONALITY", + "INPUTS", + "LINK", + "NOTES", + "OUTPUTS", + "PARAMETER", + "REMOTEHELPRUNSPACE", + "ROLE", + "SYNOPSIS" ) TestString = @' <# @@ -2821,6 +3869,7 @@ function MyFunction ($param1, $param2) It 'Should complete module specification keys in using module statement' { $res = TabExpansion2 -inputScript 'using module @{' $res.CompletionMatches.CompletionText -join ' ' | Should -BeExactly "GUID MaximumVersion ModuleName ModuleVersion RequiredVersion" + $res.CompletionMatches[0].ToolTip | Should -Not -Be $res.CompletionMatches[0].CompletionText } It 'Should not fallback to file completion when completing typenames' { @@ -2830,6 +3879,18 @@ function MyFunction ($param1, $param2) } } +Describe "TabCompletion elevated tests" -Tags CI, RequireAdminOnWindows { + It "Tab completion UNC path with spaces" -Skip:(!$IsWindows) { + $Share = New-SmbShare -Temporary -ReadAccess (whoami.exe) -Path C:\ -Name "Test Share" + $res = TabExpansion2 -inputScript '\\localhost\test' + $res.CompletionMatches[0].CompletionText | Should -BeExactly "& '\\localhost\Test Share'" + if ($null -ne $Share) + { + Remove-SmbShare -InputObject $Share -Force -Confirm:$false + } + } +} + Describe "Tab completion tests with remote Runspace" -Tags Feature,RequireAdminOnWindows { BeforeAll { $skipTest = -not $IsWindows @@ -2951,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 af013076029..6b24b8b6ce0 100644 --- a/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1 +++ b/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1 @@ -80,6 +80,49 @@ Describe 'Classes inheritance syntax' -Tags "CI" { $getter.Attributes -band [System.Reflection.MethodAttributes]::Virtual | Should -Be ([System.Reflection.MethodAttributes]::Virtual) } + It 'can implement .NET interface static properties' { + Add-Type -TypeDefinition @' +public interface IInterfaceWithStaticAbstractProperty +{ + static abstract int Getter { get; } + static abstract int Setter { get; set; } +} + +public static class InterfaceStaticAbstractPropertyTest +{ + public static int GetGetter<T>() where T : IInterfaceWithStaticAbstractProperty + => T.Getter; + + public static int GetSetter<T>() where T : IInterfaceWithStaticAbstractProperty + => T.Setter; + + public static int SetSetter<T>(int value) where T : IInterfaceWithStaticAbstractProperty + => T.Setter = value; +} +'@ + + $C1 = Invoke-Expression @' +class ClassWithStaticAbstractInterface : IInterfaceWithStaticAbstractProperty { + static [int]$Getter = 1 + static [int]$Setter = 2 +} + +[ClassWithStaticAbstractInterface] +'@ + + $C1::Getter | Should -Be 1 + $C1::Getter | Should -BeOfType ([int]) + $C1::Setter | Should -Be 2 + $C1::Setter | Should -BeOfType ([int]) + $C1::Setter = 3 + $C1::Setter | Should -Be 3 + + [InterfaceStaticAbstractPropertyTest]::GetGetter[ClassWithStaticAbstractInterface]() | Should -Be 1 + [InterfaceStaticAbstractPropertyTest]::GetSetter[ClassWithStaticAbstractInterface]() | Should -Be 3 + [InterfaceStaticAbstractPropertyTest]::SetSetter[ClassWithStaticAbstractInterface](4) + [InterfaceStaticAbstractPropertyTest]::GetSetter[ClassWithStaticAbstractInterface]() | Should -Be 4 + } + It 'allows use of defined later type as a property type' { class A { static [B]$b } class B : A {} @@ -672,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/Operators/ComparisonOperator.Tests.ps1 b/test/powershell/Language/Operators/ComparisonOperator.Tests.ps1 index 76069143327..ddc6498eb6d 100644 --- a/test/powershell/Language/Operators/ComparisonOperator.Tests.ps1 +++ b/test/powershell/Language/Operators/ComparisonOperator.Tests.ps1 @@ -82,6 +82,21 @@ Describe "ComparisonOperator" -Tag "CI" { param($lhs, $operator, $rhs) Invoke-Expression "$lhs $operator $rhs" | Should -BeFalse } + + It "Should be <result> for backtick comparison <lhs> <operator> <rhs>" -TestCases @( + @{ lhs = 'abc`def'; operator = '-like'; rhs = 'abc`def'; result = $false } + @{ lhs = 'abc`def'; operator = '-like'; rhs = 'abc``def'; result = $true } + @{ lhs = 'abc`def'; operator = '-like'; rhs = 'abc````def'; result = $false } + @{ lhs = 'abc``def'; operator = '-like'; rhs = 'abc````def'; result = $true } + @{ lhs = 'abc`def'; operator = '-like'; rhs = [WildcardPattern]::Escape('abc`def'); result = $true } + @{ lhs = 'abc`def'; operator = '-like'; rhs = [WildcardPattern]::Escape('abc``def'); result = $false } + @{ lhs = 'abc``def'; operator = '-like'; rhs = [WildcardPattern]::Escape('abc``def'); result = $true } + @{ lhs = 'abc``def'; operator = '-like'; rhs = [WildcardPattern]::Escape('abc````def'); result = $false } + ) { + param($lhs, $operator, $rhs, $result) + $expression = "'$lhs' $operator '$rhs'" + Invoke-Expression $expression | Should -Be $result + } } Describe "Bytewise Operator" -Tag "CI" { diff --git a/test/powershell/Language/Parser/Parsing.Tests.ps1 b/test/powershell/Language/Parser/Parsing.Tests.ps1 index d7bc39b0778..b85ef72c43e 100644 --- a/test/powershell/Language/Parser/Parsing.Tests.ps1 +++ b/test/powershell/Language/Parser/Parsing.Tests.ps1 @@ -683,6 +683,53 @@ Describe "Additional tests" -Tag CI { $result.EndBlock.Statements[0].PipelineElements[0].Expression.TypeName.FullName | Should -Be 'System.Tuple[System.String[],System.Int32[]]' } + It "Should correctly set the cached type for 'GenericTypeName.TypeName' as needed when the generic type is found in cache" { + $tks = $null + $ers = $null + $Script = '[System.Collections.Generic.List[string]]' + + ## See https://github.com/PowerShell/PowerShell/issues/24982 for details about the issue. + $result = [System.Management.Automation.Language.Parser]::ParseInput($Script, [ref]$tks, [ref]$ers) + $typeExpr = $result.EndBlock.Statements[0].PipelineElements[0].Expression + $typeExpr.TypeName.FullName | Should -Be 'System.Collections.Generic.List[string]' + $typeExpr.TypeName.TypeName.FullName | Should -Be 'System.Collections.Generic.List' + $typeExpr.TypeName.TypeName.GetReflectionType() | Should -Not -BeNullOrEmpty + $typeExpr.TypeName.TypeName.GetReflectionType().FullName | Should -Be 'System.Collections.Generic.List`1' + + $result2 = [System.Management.Automation.Language.Parser]::ParseInput($Script, [ref]$tks, [ref]$ers) + $typeExpr2 = $result2.EndBlock.Statements[0].PipelineElements[0].Expression + $typeExpr2.TypeName.FullName | Should -Be 'System.Collections.Generic.List[string]' + $typeExpr2.TypeName.TypeName.FullName | Should -Be 'System.Collections.Generic.List' + $typeExpr2.TypeName.TypeName.GetReflectionType() | Should -Not -BeNullOrEmpty + $typeExpr2.TypeName.TypeName.GetReflectionType().FullName | Should -Be 'System.Collections.Generic.List`1' + + $Script = '[System.Tuple[System.String[],System.Int32[]]]' + $result3 = [System.Management.Automation.Language.Parser]::ParseInput($Script, [ref]$tks, [ref]$ers) + $result3.EndBlock.Statements[0].PipelineElements[0].Expression.TypeName.TypeName.GetReflectionType().FullName | Should -Be 'System.Tuple' + + ## Generic type with assembly name can be resolved. + [System.Collections.Generic.List[string], System.Private.CoreLib].FullName | Should -BeLike 'System.Collections.Generic.List``1`[`[System.String, *`]`]' + + $Script = '[System.Collections.Generic.List[string], System.Private.CoreLib]' + $result4 = [System.Management.Automation.Language.Parser]::ParseInput($Script, [ref]$tks, [ref]$ers) + $typeExpr4 = $result4.EndBlock.Statements[0].PipelineElements[0].Expression + $typeExpr4.TypeName.FullName | Should -Be 'System.Collections.Generic.List[string],System.Private.CoreLib' + $typeExpr4.TypeName.TypeName.FullName | Should -Be 'System.Collections.Generic.List,System.Private.CoreLib' + $typeExpr4.TypeName.TypeName.GetReflectionType() | Should -Not -BeNullOrEmpty + $typeExpr4.TypeName.TypeName.GetReflectionType().FullName | Should -Be 'System.Collections.Generic.List`1' + + ## Generic type with '`<n>' in name can be resolved. + [System.Collections.Generic.List`1[string]].FullName | Should -BeLike 'System.Collections.Generic.List``1`[`[System.String, *`]`]' + + $Script = '[System.Collections.Generic.List`1[string]]' + $result5 = [System.Management.Automation.Language.Parser]::ParseInput($Script, [ref]$tks, [ref]$ers) + $typeExpr5 = $result5.EndBlock.Statements[0].PipelineElements[0].Expression + $typeExpr5.TypeName.FullName | Should -Be 'System.Collections.Generic.List`1[string]' + $typeExpr5.TypeName.TypeName.FullName | Should -Be 'System.Collections.Generic.List`1' + $typeExpr5.TypeName.TypeName.GetReflectionType() | Should -Not -BeNullOrEmpty + $typeExpr5.TypeName.TypeName.GetReflectionType().FullName | Should -Be 'System.Collections.Generic.List`1' + } + It "Should get correct offsets for number constant parsing error" { $tks = $null $ers = $null 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/PipelineStoppedToken.Tests.ps1 b/test/powershell/Language/Scripting/PipelineStoppedToken.Tests.ps1 new file mode 100644 index 00000000000..54d1510a20d --- /dev/null +++ b/test/powershell/Language/Scripting/PipelineStoppedToken.Tests.ps1 @@ -0,0 +1,97 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +Describe 'PipelineStopToken tests' -Tags 'CI' { + + BeforeAll { + Function Invoke-WithStop { + [CmdletBinding()] + param ( + [Parameter(Mandatory)] + [ScriptBlock] + $ScriptBlock, + + [Parameter(ValueFromRemainingArguments)] + [object[]] + $ArgumentList + ) + + $ps = [PowerShell]::Create() + $null = $ps.AddScript("'start'`n" + $ScriptBlock.ToString()) + foreach ($arg in $ArgumentList) { + $null = $ps.AddArgument($arg) + } + + $inPipe = [System.Management.Automation.PSDataCollection[object]]::new() + $inPipe.Complete() + $outPipe = [System.Management.Automation.PSDataCollection[object]]::new() + + # Use an event to make sure Stop is called once the pipeline has started + # and not before. + $eventId = [Guid]::NewGuid().ToString() + Register-ObjectEvent -InputObject $outPipe -EventName DataAdded -SourceIdentifier $eventId + try { + $task = $ps.BeginInvoke($inPipe, $outPipe) + Wait-Event -SourceIdentifier $eventId | Remove-Event + } + finally { + Remove-Event -SourceIdentifier $eventId -ErrorAction SilentlyContinue + Unregister-Event -SourceIdentifier $eventId + } + + $ps.Stop() + $ps.Streams.Error | Write-Error + { $ps.EndInvoke($task) } | Should -Throw -ErrorId PipelineStoppedException + } + } + + It 'Signal advanced function to stop' { + $start = Get-Date + + Invoke-WithStop -ScriptBlock { + Function Test-FunctionWithStop { + [CmdletBinding()] + param ([Parameter()][int]$Timeout) + + [System.Threading.Tasks.Task]::Delay($Timeout * 1000, $PSCmdlet.PipelineStopToken).GetAwaiter().GetResult() + } + + Test-FunctionWithStop -Timeout 10 + } + + $end = (Get-Date) - $start + $end.TotalSeconds | Should -BeLessThan 10 + } + + It 'Signals compiled cmdlet to stop' { + $binaryAssembly = Add-Type @' +using System; +using System.Management.Automation; +using System.Threading.Tasks; + +namespace PipelineStoppedToken.Tests; + +[Cmdlet(VerbsDiagnostic.Test, "CmdletWithStop")] +public sealed class TestCmdletWithStop : Cmdlet +{ + [Parameter] + public int Timeout { get; set; } + + protected override void EndProcessing() + { + Task.Delay(Timeout * 1000, PipelineStopToken).GetAwaiter().GetResult(); + } +} +'@ -PassThru | ForEach-Object Assembly | Select-Object -First 1 + + $start = Get-Date + Invoke-WithStop -ScriptBlock { + Import-Module -Assembly $args[0] + + Test-CmdletWithStop -Timeout 10 + } -ArgumentList $binaryAssembly + + $end = (Get-Date) - $start + $end.TotalSeconds | Should -BeLessThan 10 + } +} diff --git a/test/powershell/Language/Scripting/Requires.Tests.ps1 b/test/powershell/Language/Scripting/Requires.Tests.ps1 index 30a600fe4b7..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" + $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/Get-Command.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Core/Get-Command.Tests.ps1 index 600d04cef6f..1fa33efbf0d 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Core/Get-Command.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Core/Get-Command.Tests.ps1 @@ -269,4 +269,9 @@ Describe "Get-Command Tests" -Tags "CI" { $result.Count | Should -Be 2 $result.Name | Should -Be "Add-Content","Get-Content" } + + It "Excluding modules works" { + $result = Get-Command -Name Get-Command -ExcludeModule Microsoft.PowerShell.Core + $result | Should -Be $null + } } diff --git a/test/powershell/Modules/Microsoft.PowerShell.Core/Import-Module.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Core/Import-Module.Tests.ps1 index 6885abe847d..19dc80caf28 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Core/Import-Module.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Core/Import-Module.Tests.ps1 @@ -62,6 +62,7 @@ Describe "Import-Module" -Tags "CI" { 'X86' { 'x86' } 'X64' { 'amd64' } 'Arm64' { 'arm' } + 'Arm' { 'arm' } default { throw "Unknown processor architecture" } } New-ModuleManifest -Path "$TestDrive\TestModule.psd1" -ProcessorArchitecture $currentProcessorArchitecture diff --git a/test/powershell/Modules/Microsoft.PowerShell.Core/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 b43d7033ca9..3899ace7e3b 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/FileSystem.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/FileSystem.Tests.ps1 @@ -1,5 +1,8 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. + +Import-Module HelpersCommon + Describe "Basic FileSystem Provider Tests" -Tags "CI" { BeforeAll { $testDir = "TestDir" @@ -602,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" @@ -621,6 +625,11 @@ 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 ([WildcardPattern]::Escape($realFile)), ([WildcardPattern]::Escape($realDir)), ([WildcardPattern]::Escape($realDir2))) -Recurse $TestPath/* + } + It "New-Item can create a hard link to a file" { New-Item -ItemType HardLink -Path $hardLinkToFile -Value $realFile > $null Test-Path $hardLinkToFile | Should -BeTrue @@ -631,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() @@ -650,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() @@ -658,12 +667,37 @@ 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" $link.Target | Should -BeExactly $real.ToString() } + + It "New-Item can create a directory symbolic link to a directory using a relative path" -Skip:(-Not $IsWindows) { + $target = Split-Path -Leaf $realDir + New-Item -ItemType SymbolicLink -Path $symLinkToDir -Value $target > $null + Test-Path $symLinkToDir | Should -BeTrue + $real = Get-Item -LiteralPath $realDir + $link = Get-Item -Path $symLinkToDir + $link | Should -BeOfType System.IO.DirectoryInfo + $link.LinkType | Should -BeExactly "SymbolicLink" + $link.ResolvedTarget | Should -BeExactly $real.ToString() + $link.Target | Should -BeExactly $target + } + + It "New-Item can create a directory symbolic link to a directory using a relative path with .\" -Skip:(-Not $IsWindows) { + $target = ".\$(Split-Path -Leaf $realDir)" + New-Item -ItemType SymbolicLink -Path $symLinkToDir -Value $target > $null + Test-Path $symLinkToDir | Should -BeTrue + $real = Get-Item -LiteralPath $realDir + $link = Get-Item -Path $symLinkToDir + $link | Should -BeOfType System.IO.DirectoryInfo + $link.LinkType | Should -BeExactly "SymbolicLink" + $link.ResolvedTarget | Should -BeExactly $real.ToString() + $link.Target | Should -BeExactly $target + } + It "New-Item can create a directory junction to a directory" -Skip:(-Not $IsWindows) { New-Item -ItemType Junction -Path $junctionToDir -Value $realDir > $null Test-Path $junctionToDir | Should -BeTrue @@ -699,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" @@ -846,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 } } @@ -1172,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-Item.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Get-Item.Tests.ps1 index bb701855058..4ddceaa6836 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Get-Item.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Get-Item.Tests.ps1 @@ -111,7 +111,7 @@ Describe "Get-Item" -Tags "CI" { { return } - $altStreamPath = "$TESTDRIVE/altStream.txt" + $altStreamPath = "$TESTDRIVE\altStream.txt" $altStreamDirectory = "$TESTDRIVE/altstreamdir" $noAltStreamDirectory = "$TESTDRIVE/noaltstreamdir" $stringData = "test data" @@ -144,6 +144,10 @@ Describe "Get-Item" -Tags "CI" { $result = Get-Item $noAltStreamDirectory -Stream * -ErrorAction Stop $result | Should -BeExactly $null } + It "Should return filename property correctly" -Skip:$skipNotWindows { + $result = (Get-Item -Path $altStreamPath -Stream $streamName).FileName + $result | Should -BeExactly $altStreamPath + } } Context "Registry Provider" { diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Get-PSDrive.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Get-PSDrive.Tests.ps1 index f2684773a15..4ad46de7cbb 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Get-PSDrive.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Get-PSDrive.Tests.ps1 @@ -66,7 +66,7 @@ Describe "Temp: drive" -Tag Feature { } } -Describe "Get-PSDrive for network path" -Tags "Feature","RequireAdminOnWindows" { +Describe "Get-PSDrive for network path" -Tags "CI","RequireAdminOnWindows" { It 'Check P/Invoke GetDosDevice/QueryDosDevice' -Skip:(-not $IsWindows) { $UsedDrives = Get-PSDrive | Select-Object -ExpandProperty Name @@ -78,4 +78,39 @@ Describe "Get-PSDrive for network path" -Tags "Feature","RequireAdminOnWindows" $drive.DisplayRoot | Should -BeExactly '\\localhost\c$\Windows' subst "$($PSDriveName):" /D } + + It 'Check P/Invoke for WNetGetConnection with small buffer: <SmallBuffer>' -Skip:(-not $IsWindows) -TestCases @( + @{ SmallBuffer = $false } + @{ SmallBuffer = $true } + ) { + param ($SmallBuffer) + + $UsedDrives = Get-PSDrive | Select-Object -ExpandProperty Name + $PSDriveName = 'D'..'Z' | Where-Object -FilterScript {$_ -notin $UsedDrives} | Get-Random + + $drive = New-PSDrive -Name $PSDriveName -PSProvider FileSystem -Root \\localhost\c$\Windows -Persist + try { + if ($SmallBuffer) { + [System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('WNetGetConnectionBufferSize', 4) + } + + # The result is cached in the current instance, use a new + # PowerShell instance to test out WNetGetConnection code. + $ps = [PowerShell]::Create() + $actual = $ps.AddCommand('Get-PSDrive').AddParameter('Name', $PSDriveName).Invoke() + if ($ps.HadErrors) { + throw $ps.Streams.Error[0] + } + + $actual.Name | Should -BeExactly $PSDriveName + $actual.DisplayRoot | Should -BeExactly '\\localhost\c$\Windows' + } + finally { + $drive | Remove-PSDrive + + if ($SmallBuffer) { + [System.Management.Automation.Internal.InternalTestHooks]::SetTestHook('WNetGetConnectionBufferSize', -1) + } + } + } } 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 8cb6391cd7b..9b0898fee90 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Join-Path.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Join-Path.Tests.ps1 @@ -50,4 +50,136 @@ Describe "Join-Path cmdlet tests" -Tags "CI" { $result.Count | Should -Be 1 $result | Should -BeExactly "one${sepChar}two${sepChar}three${sepChar}four${sepChar}five" } + It "Join-Path -Path <Path> -ChildPath <ChildPath> should return '<ExpectedResult>'" -TestCases @( + @{ + Path = 'one' + ChildPath = 'two', 'three' + ExpectedResult = "one${sepChar}two${sepChar}three" + } + @{ + Path = 'one', 'two' + ChildPath = 'three', 'four' + ExpectedResult = @( + "one${sepChar}three${sepChar}four" + "two${sepChar}three${sepChar}four" + ) + } + @{ + Path = 'one' + ChildPath = @() + ExpectedResult = "one${sepChar}" + } + @{ + Path = 'one' + ChildPath = $null + ExpectedResult = "one${sepChar}" + } + @{ + Path = 'one' + ChildPath = [string]::Empty + ExpectedResult = "one${sepChar}" + } + ) { + param($Path, $ChildPath, $ExpectedResult) + $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/New-Item.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/New-Item.Tests.ps1 index 89f21d5e055..5cd5a660c8b 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/New-Item.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/New-Item.Tests.ps1 @@ -386,3 +386,13 @@ Describe "New-Item -Force allows to create an item even if the directories in th $FullyQualifiedFile | Should -Exist } } + +Describe "New-Item -Force should throw an error for invalid characters in directory path" -Tags "CI" { + BeforeAll { + $invalidPath = Join-Path -Path $TestDrive -ChildPath 'Invalid?' + } + + It "Should throw an error when -Force is used with an invalid directory path" -Skip:(!$IsWindows) { + { New-Item -Path $invalidPath -ItemType Directory -Force -ErrorAction Stop } | Should -Throw -ErrorId 'CreateDirectoryIOError,Microsoft.PowerShell.Commands.NewItemCommand' + } +} diff --git a/test/powershell/Modules/Microsoft.PowerShell.Management/Registry.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Registry.Tests.ps1 index 635e3f2940f..06e2cf81ac0 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Registry.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Registry.Tests.ps1 @@ -471,6 +471,47 @@ Describe "Extended Registry Provider Tests" -Tags @("Feature", "RequireAdminOnWi } } } + + Context "Validate Get-ItemProperty Cast Exception" { + BeforeAll { + if ($IsWindows) { + $registrySubkeyPath = 'HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\badreg' + + # Below will import .reg file with 64 bit integer in 32 bit DWORD + $badRegistryContent = @" +Windows Registry Editor Version 5.00 + +[$registrySubkeyPath] +"NoModify"=hex(4):01,00,00,00,00,00,00,00 +"@ + + $badRegistryPath = Join-Path -Path $TestDrive -ChildPath badreg.reg + $badRegistryContent | Set-Content -Path $badRegistryPath + reg.exe import $badRegistryPath + + $registryProviderSubkeyPath = 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\badreg' + } + } + + It "Validate non-terminating error for cast" { + Get-ItemProperty -Path $registryProviderSubkeyPath -ErrorVariable err -ErrorAction SilentlyContinue + $err | Should -HaveCount 1 + $err[0].Exception | Should -BeOfType [System.InvalidCastException] + $err[0].TargetObject | Should -BeExactly $registrySubkeyPath + $err[0].CategoryInfo.Category | Should -BeExactly 'ReadError' + $err[0].FullyQualifiedErrorId | Should -BeExactly 'System.InvalidCastException,Microsoft.PowerShell.Commands.GetItemPropertyCommand' + } + + It "Validate terminating error for cast" { + { Get-ItemProperty -Path $registryProviderSubkeyPath -ErrorAction Stop } | Should -Throw -ErrorId 'System.InvalidCastException,Microsoft.PowerShell.Commands.GetItemPropertyCommand' + } + + AfterAll { + if ($IsWindows) { + reg.exe delete $registrySubkeyPath /f + } + } + } } } finally { 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/Start-Process.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Management/Start-Process.Tests.ps1 index 50cde0bae6e..65dd74e1b94 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Management/Start-Process.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Management/Start-Process.Tests.ps1 @@ -241,3 +241,17 @@ Describe "Environment Tests" -Tags "Feature" { } } } + +Describe "Bug fixes" -Tags "CI" { + + ## https://github.com/PowerShell/PowerShell/issues/24986 + It "Error redirection along with '-NoNewWindow' should work for Start-Process" -Skip:(!$IsWindows) { + $errorFile = Join-Path -Path $TestDrive -ChildPath error.txt + $out = pwsh -noprofile -c "Start-Process -Wait -NoNewWindow -RedirectStandardError $errorFile -FilePath cmd -ArgumentList '/C echo Hello'" + + ## 'Hello' should be sent to standard output; 'error.txt' file should be created but empty. + $out | Should -BeExactly "Hello" + Test-Path -Path $errorFile | Should -BeTrue + (Get-Item $errorFile).Length | Should -Be 0 + } +} 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/CertificateProvider.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Security/CertificateProvider.Tests.ps1 index c6c468e5f0b..191443bde9e 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Security/CertificateProvider.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Security/CertificateProvider.Tests.ps1 @@ -292,4 +292,80 @@ Describe "Certificate Provider tests" -Tags "Feature" { $certs.Thumbprint | Should -BeExactly $thumbprint } } + + Context "SAN DNS Name Tests" { + BeforeAll { + $configFilePath = Join-Path -Path $TestDrive -ChildPath 'openssl.cnf' + $keyFilePath = Join-Path -Path $TestDrive -ChildPath 'privateKey.key' + $certFilePath = Join-Path -Path $TestDrive -ChildPath 'certificate.crt' + $pfxFilePath = Join-Path -Path $TestDrive -ChildPath 'certificate.pfx' + $password = New-CertificatePassword | ConvertFrom-SecureString -AsPlainText + + $config = @" + [ req ] + default_bits = 2048 + distinguished_name = req_distinguished_name + req_extensions = v3_req + prompt = no + + [ req_distinguished_name ] + CN = yourdomain.com + + [ v3_req ] + subjectAltName = @alt_names + + [ alt_names ] + DNS.1 = yourdomain.com + DNS.2 = www.yourdomain.com + DNS.3 = api.yourdomain.com + DNS.4 = xn--mnchen-3ya.com + DNS.5 = xn--80aaxitdbjr.com + DNS.6 = xn--caf-dma.com +"@ + + # Write the configuration to the specified path + Set-Content -Path $configFilePath -Value $config + + # Generate the self-signed certificate with SANs + openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout $keyFilePath -out $certFilePath -config $configFilePath -extensions v3_req + + # Create the PFX file + openssl pkcs12 -export -out $pfxFilePath -inkey $keyFilePath -in $certFilePath -passout pass:$password + } + + It "Should set DNSNameList from SAN extensions" { + $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($pfxFilePath, $password) + + $expectedDnsNameList = @( + [PSCustomObject]@{ + Punycode = "yourdomain.com" + Unicode = "yourdomain.com" + } + [PSCustomObject]@{ + Punycode = "www.yourdomain.com" + Unicode = "www.yourdomain.com" + } + [PSCustomObject]@{ + Punycode = "api.yourdomain.com" + Unicode = "api.yourdomain.com" + } + [PSCustomObject]@{ + Punycode = "xn--mnchen-3ya.com" + Unicode = "münchen.com" + } + [PSCustomObject]@{ + Punycode = "xn--80aaxitdbjr.com" + Unicode = "папитрока.com" + } + [PSCustomObject]@{ + Punycode = "xn--caf-dma.com" + Unicode = "café.com" + } + ) + + $cert | Should -Not -BeNullOrEmpty + $cert.DnsNameList | Should -HaveCount 6 + ($cert.DnsNameList | ConvertTo-Json -Compress) | Should -BeExactly ($expectedDnsNameList | ConvertTo-Json -Compress) + } + } } 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 4b459fe67d9..0c15fe6d359 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Implicit.Remoting.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Implicit.Remoting.Tests.ps1 @@ -1633,7 +1633,7 @@ try try { Invoke-Command $session { function attack(${foo="$(calc)"}){Write-Output "It is done."}} $module = Import-PSSession -Session $session -CommandName attack -ErrorAction SilentlyContinue -ErrorVariable expectedError -AllowClobber - $expectedError | Should -Not -BeNullOrEmpty + $expectedError | Should -Not -Be $null } finally { if ($null -ne $module) { Remove-Module $module -Force -ErrorAction SilentlyContinue } } @@ -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/Write-Host.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Write-Host.Tests.ps1 index a75c17bcbaa..92b6a027853 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Write-Host.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Write-Host.Tests.ps1 @@ -60,6 +60,8 @@ Describe "Write-Host with TestHostCS" -Tags "CI" { @{ Name = '-Separator, colors and -NoNewLine'; Command = "Write-Host a,b,c -Separator ',' -ForegroundColor Yellow -BackgroundColor DarkBlue -NoNewline"; returnCount = 1; returnValue = @("Yellow:DarkBlue:a,b,c:NoNewLine"); returnInfo = @("a,b,c") } @{ Name = '-NoNewline:$true and colors'; Command = "Write-Host a,b -NoNewline:`$true -ForegroundColor Red -BackgroundColor Green;Write-Host a,b"; returnCount = 2; returnValue = @("Red:Green:a b:NoNewLine", "White:Black:a b:NewLine"); returnInfo = @("a b", "a b") } @{ Name = '-NoNewline:$false and colors'; Command = "Write-Host a,b -NoNewline:`$false -ForegroundColor Red -BackgroundColor Green;Write-Host a,b"; returnCount = 2; returnValue = @("Red:Green:a b:NewLine","White:Black:a b:NewLine"); returnInfo = @("a b", "a b") } + @{ Name = 'XMLElement'; Command = "Write-Host ([xml] '<OhElement>Where art thou?</OhElement>').DocumentElement"; returnCount = 1; returnValue = @("White:Black:OhElement:NewLine"); returnInfo = @("OhElement") } + @{ Name = 'XMLDocument'; Command = "Write-Host ([system.xml.xmldocument] '<OhElement>Where art thou?</OhElement>').DocumentElement"; returnCount = 1; returnValue = @("White:Black:OhElement:NewLine"); returnInfo = @("OhElement") } ) } 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.Powershell.Host/Start-Transcript.Tests.ps1 b/test/powershell/Modules/Microsoft.Powershell.Host/Start-Transcript.Tests.ps1 index 3e398a0fa70..8e142c7655f 100644 --- a/test/powershell/Modules/Microsoft.Powershell.Host/Start-Transcript.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.Powershell.Host/Start-Transcript.Tests.ps1 @@ -86,6 +86,11 @@ Describe "Start-Transcript, Stop-Transcript tests" -tags "CI" { $outputFilePath = Join-Path $TestDrive "PowerShell_transcript*" ValidateTranscription -scriptToExecute $script -outputFilePath $outputFilePath } + It "Should create Transcript file with 'Transcript' preference variable" { + # Casting to PSObject is necessary because Set-Variable does not automatically wrap the value in a PSObject + $script = "Set-Variable -Scope Global -Name Transcript -Value ([PSObject]'$transcriptFilePath'); Start-Transcript" + ValidateTranscription -scriptToExecute $script -outputFilePath $transcriptFilePath + } It "Should Append Transcript data in existing file if 'Append' parameter is used with Path parameter" { $script = "Start-Transcript -path $transcriptFilePath -Append" ValidateTranscription -scriptToExecute $script -outputFilePath $transcriptFilePath -append 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/Api/TypeInference.Tests.ps1 b/test/powershell/engine/Api/TypeInference.Tests.ps1 index 7676ecf3197..fc6bdae74b8 100644 --- a/test/powershell/engine/Api/TypeInference.Tests.ps1 +++ b/test/powershell/engine/Api/TypeInference.Tests.ps1 @@ -268,6 +268,66 @@ Describe "Type inference Tests" -tags "CI" { $res.Name | Should -Be 'System.Management.ManagementObject#root\cimv2\Win32_Process' } + It "Infers type from parameter in classic function definition" { + $res = [AstTypeInference]::InferTypeOf(({ + function MyFunction ([int]$param1) + { + $param1 + } + }.Ast.FindAll({param($Ast) $Ast -is [Language.VariableExpressionAst]}, $true) | Select-Object -Last 1 )) + $res.Name | Should -Be 'System.Int32' + } + + It "Infers type from binary expression with a bool operator as bool" { + $res = [AstTypeInference]::InferTypeOf( { + (1..10) -contains 5 + }.Ast) + $res.Count | Should -Be 1 + $res.Name | Should -Be 'System.Boolean' + } + + It "Infers type from binary expression with a string operator as string" { + $res = [AstTypeInference]::InferTypeOf( { + (1..10) -join ',' + }.Ast) + $res.Count | Should -Be 1 + $res.Name | Should -Be 'System.String' + } + + It "Infers type from binary expression with a split operator as string array" { + $res = [AstTypeInference]::InferTypeOf( { + "Test:Value" -split ':' + }.Ast) + $res.Count | Should -Be 1 + $res.Name | Should -Be 'System.String[]' + } + + It "Infers type from binary expression with a comparison operator as bool + the left hand side type" { + $res = [AstTypeInference]::InferTypeOf( { + ("Hello", "World") -eq "Hello" + }.Ast) + $res.Count | Should -Be 2 + $res.Name[0] | Should -Be 'System.Boolean' + $res.Name[1] | Should -Be 'System.String[]' + } + + It "Infers type from binary expression with a null coalescing operator as left and right hand side types" { + $res = [AstTypeInference]::InferTypeOf( { + "NotNull" ?? 10 + }.Ast) + $res.Count | Should -Be 2 + $res.Name[0] | Should -Be 'System.String' + $res.Name[1] | Should -Be 'System.Int32' + } + + It "Infers type from binary expression with an overridden operator" { + $res = [AstTypeInference]::InferTypeOf( { + (Get-Date) - (Get-Date) + }.Ast) + $res.Count | Should -Be 1 + $res.Name | Should -Be 'System.TimeSpan' + } + It "Infers type from DATA statement" { $res = [AstTypeInference]::InferTypeOf( { DATA { @@ -361,10 +421,8 @@ Describe "Type inference Tests" -tags "CI" { It "Infers type from foreach-object of integer" { $res = [AstTypeInference]::InferTypeOf( { [int[]] $i = 1..20; $i | ForEach-Object {$_ * 10} }.Ast) - $res.Count | Should -Be 2 - foreach ($r in $res) { - $r.Name -In 'System.Int32', 'System.Int32[]' | Should -BeTrue - } + $res.Count | Should -Be 1 + $res.Name | Should -Be 'System.Int32' } It "Infers type from generic new" { @@ -386,9 +444,9 @@ Describe "Type inference Tests" -tags "CI" { It "Infers type from foreach-object with begin/end" { $res = [AstTypeInference]::InferTypeOf( { [int[]] $i = 1..20; $i | ForEach-Object -Begin {"Hi"} {$_ * 10} -End {[int]} }.Ast) - $res.Count | Should -Be 4 + $res.Count | Should -Be 3 foreach ($r in $res) { - $r.Name -In 'System.Int32', 'System.Int32[]', 'System.String', 'System.Type' | Should -BeTrue + $r.Name -In 'System.Int32', 'System.String', 'System.Type' | Should -BeTrue } } @@ -444,6 +502,14 @@ Describe "Type inference Tests" -tags "CI" { } } + It 'Infers typeof pipeline chain' { + $ast = {New-TimeSpan && New-Guid}.Ast + $typeNames = [AstTypeInference]::InferTypeof($ast, [TypeInferenceRuntimePermissions]::AllowSafeEval) + $typeNames.Count | Should -Be 2 + $typeNames[0] | Should -Be 'System.TimeSpan' + $typeNames[1] | Should -Be 'System.Guid' + } + It "Infers typeof pscustomobject" { $res = [AstTypeInference]::InferTypeOf( { [pscustomobject] @{ @@ -594,8 +660,10 @@ Describe "Type inference Tests" -tags "CI" { } It "Infers type from variable with AllowSafeEval" { - function Hide-GetProcess { Get-Process } - $p = Hide-GetProcess + # Invoke-Expression is used to "hide" Get-Process from the type inference. + # If the typeinference code is updated to handle Invoke-Expression, this test will need to find some other way to set $p + # so that the type inference can't figure it out without evaluating the variable value + $p = Invoke-Expression -Command 'Get-Process' $res = [AstTypeInference]::InferTypeOf( { $p }.Ast, [TypeInferenceRuntimePermissions]::AllowSafeEval) $res.Name | Should -Be 'System.Diagnostics.Process' } @@ -618,16 +686,6 @@ Describe "Type inference Tests" -tags "CI" { $res.Name | Should -Be 'System.Int32' } - It 'Infers type from attributed expession' { - $res = [AstTypeInference]::InferTypeOf( { - [ValidateRange(1, 2)] - [int]$i = 1 - }.Ast) - - $res.Count | Should -Be 1 - $res.Name | Should -Be System.Int32 - } - It 'Infers type from if statement' { $res = [AstTypeInference]::InferTypeOf( { if ($true) { return 1} @@ -1139,6 +1197,18 @@ Describe "Type inference Tests" -tags "CI" { $res.Name | Should -Be System.String } + It 'Ignores assignment when a variable is declared and used within the same commandAst' { + $variableAst = { Get-Random 2>variable:RandomError1 -InputObject ($RandomError1) }.Ast.FindAll({ param($a) $a -is [Language.VariableExpressionAst] }, $true) | select -Last 1 + $res = [AstTypeInference]::InferTypeOf($variableAst) + $res.Count | Should -Be 0 + } + + It 'Ignores the last assignment when a variable is reused' { + $variableAst = { $x = New-Guid; $x = $x.Where{$_} }.Ast.FindAll({ param($a) $a -is [Language.VariableExpressionAst] }, $true) | select -Last 1 + $res = [AstTypeInference]::InferTypeOf($variableAst) + $res.Name | Should -Be System.Guid + } + $catchClauseTypes = @( @{ Type = 'System.ArgumentException' } @{ Type = 'System.ArgumentNullException' } @@ -1399,7 +1469,8 @@ Describe "Type inference Tests" -tags "CI" { It 'Infers closest variable type' { $res = [AstTypeInference]::InferTypeOf( { [string]$TestVar = "";[hashtable]$TestVar = @{};$TestVar }.Ast) - $res.Name | Select-Object -Last 1 | Should -Be "System.Collections.Hashtable" + $res.Count | Should -Be 1 + $res.Name | Should -Be "System.Collections.Hashtable" } It 'Infers closest variable type and ignores unrelated param blocks' { @@ -1420,6 +1491,26 @@ Describe "Type inference Tests" -tags "CI" { $res.Count | Should -Be 0 } + It 'Infers right side of assignment expression' { + $res = [AstTypeInference]::InferTypeOf( { $Test1 = "Hello" }.Ast.Find({param($ast) $ast -is [Language.AssignmentStatementAst]}, $true)) + $res.Count | Should -Be 1 + $res.Name | Should -Be "System.String" + } + + It 'Infers left side of assignment expression when it is a ConvertExpression' { + $res = [AstTypeInference]::InferTypeOf( { [string]$Test1 = 42 }.Ast.Find({param($ast) $ast -is [Language.AssignmentStatementAst]}, $true)) + $res.Count | Should -Be 1 + $res.Name | Should -Be "System.String" + } + + It 'Infers left side of assignment expression when there is a ConvertExpression among other attributes' { + $res = [AstTypeInference]::InferTypeOf( { + [ValidateLength()] [string] [ValidatePattern()]$Test1 = 42 + }.Ast.Find({param($ast) $ast -is [Language.AssignmentStatementAst]}, $true)) + $res.Count | Should -Be 1 + $res.Name | Should -Be "System.String" + } + It 'Infers type of all scope variable after variable assignment' { $res = [AstTypeInference]::InferTypeOf( { $true = "Hello";$true }.Ast) $res.Count | Should -Be 1 @@ -1432,6 +1523,30 @@ Describe "Type inference Tests" -tags "CI" { $res.Name | Should -Be 'System.Management.Automation.Internal.Host.InternalHost' } + It 'Infers type of variable assigned inside do while loop' { + $res = [AstTypeInference]::InferTypeOf(({ + do + { + $Test = 1 + $Test + } + while (1) + }.Ast.FindAll({param($Ast) $Ast -is [Language.VariableExpressionAst]}, $true) | Select-Object -Last 1 )) + $res.Name | Should -Be 'System.Int32' + } + + It 'Infers type of variable assigned inside do until loop' { + $res = [AstTypeInference]::InferTypeOf(({ + do + { + $Test = 1 + $Test + } + until ($null = gci) + }.Ast.FindAll({param($Ast) $Ast -is [Language.VariableExpressionAst] -and $Ast.VariablePath.UserPath -eq 'Test'}, $true) | Select-Object -Last 1 )) + $res.Name | Should -Be 'System.Int32' + } + It 'Infers type of external applications' { $res = [AstTypeInference]::InferTypeOf( { pwsh }.Ast) $res.Name | Should -Be 'System.String' @@ -1444,6 +1559,290 @@ Describe "Type inference Tests" -tags "CI" { ) $null = [AstTypeInference]::InferTypeOf($FoundAst) } + + It 'Ignores type constraint defined outside of scope' { + $res = [AstTypeInference]::InferTypeOf(({ + function Outer + { + [string] $Test = "Hello" + function Inner + { + $Test = 2 + $Test + } + } + }.Ast.FindAll({param($Ast) $Ast -is [Language.VariableExpressionAst]}, $true) | Select-Object -Last 1 )) + $res.Name | Should -Be 'System.Int32' + } + + It 'Considers the type constraint defined outside of scope when dot sourcing' { + $res = [AstTypeInference]::InferTypeOf(({ + [string] $Test = "Hello" + . {$Test = 2; $Test} + }.Ast.FindAll({param($Ast) $Ast -is [Language.VariableExpressionAst]}, $true) | Select-Object -Last 1 )) + $res.Name | Should -Be 'System.String' + } + + It 'Infers type of ref assigned variable' { + $res = [AstTypeInference]::InferTypeOf(({ + $MyRefVar = $null + $null = [System.Management.Automation.Language.Parser]::ParseInput("", [ref] $MyRefVar, [ref] $null) + $MyRefVar + }.Ast.FindAll({param($Ast) $Ast -is [Language.VariableExpressionAst]}, $true) | Select-Object -Last 1 )) + $res.Name | Should -Be 'System.Management.Automation.Language.Token[]' + } + + It 'Infers type of variable assigned with New/Set-Variable' { + $res = [AstTypeInference]::InferTypeOf( { + New-Variable -Name Var1 -Value $true | Out-Null + New-Variable -Name Var2 -Value "Hello" | Out-Null + $Var1 + $Var2 + }.Ast) + $res[0].Name | Should -Be 'System.Boolean' + $res[1].Name | Should -Be 'System.String' + } + + It 'Infers type of variable assigned with <ParameterName> common parameter' -TestCases @( + @{ParameterName = "WarningVariable"; ExpectedType = [List[WarningRecord]]} + @{ParameterName = "wv"; ExpectedType = [List[WarningRecord]]} + @{ParameterName = "ErrorVariable"; ExpectedType = [List[ErrorRecord]]} + @{ParameterName = "ev"; ExpectedType = [List[ErrorRecord]]} + @{ParameterName = "InformationVariable"; ExpectedType = [List[InformationalRecord]]} + @{ParameterName = "iv"; ExpectedType = [List[InformationalRecord]]} + @{ParameterName = "OutVariable"; ExpectedType = [guid]} + @{ParameterName = "ov"; ExpectedType = [guid]} + @{ParameterName = "PipelineVariable"; ExpectedType = [guid]} + @{ParameterName = "pv"; ExpectedType = [guid]} + ) -Test { + param($ParameterName, $ExpectedType) + $Ast = [scriptblock]::Create("New-Guid -$ParameterName MyOutVar | % {`$MyOutVar}").Ast.FindAll({ + param($Ast) + $Ast -is [Language.VariableExpressionAst] + }, $true) | Select-Object -Last 1 + $res = [AstTypeInference]::InferTypeOf($Ast) + $res.Type | Should -Be $ExpectedType + } + + It 'Infers type of variable assigned via Data statement' { + $res = [AstTypeInference]::InferTypeOf(({ + Data MyDataVar {"Hello"} + $MyDataVar + }.Ast.FindAll({param($Ast) $Ast -is [Language.VariableExpressionAst]}, $true) | Select-Object -Last 1 )) + $res.Name | Should -Be 'System.String' + } + + It 'Infers type of well known variable with global scope' { + $res = [AstTypeInference]::InferTypeOf({$global:true}.Ast) + $res.Name | Should -Be 'System.Boolean' + } + + It 'Infers parameter type from closest parameter' { + $res = [AstTypeInference]::InferTypeOf( ({ + param([string]$Param1) + function TestFunction {param([bool]$Param1) $Param1} + }.Ast.FindAll({param($Ast) $Ast -is [Language.VariableExpressionAst]}, $true) | Select-Object -Last 1 )) + $res.Name | Should -Be 'System.Boolean' + } + + It 'Infers variable type from closest foreach statement' { + $res = [AstTypeInference]::InferTypeOf( ({ + foreach ($X in 1..10) + { + $X + } + foreach ($X in New-Guid) + { + $X + } + }.Ast.FindAll({param($Ast) $Ast -is [Language.VariableExpressionAst]}, $true) | Select-Object -Last 1 )) + $res.Name | Should -Be 'System.Guid' + } + + It 'Infers global variable type in child scope' { + $res = [AstTypeInference]::InferTypeOf( ({ + $Global:GlobalTest1 = "Hello" + function TestFunction {$GlobalTest1} + }.Ast.FindAll({param($Ast) $Ast -is [Language.VariableExpressionAst]}, $true) | Select-Object -Last 1 )) + $res.Name | Should -Be 'System.String' + } + + It 'Does not infer private variable type in child scope' { + $res = [AstTypeInference]::InferTypeOf( ({ + $Private:PrivateTest1 = "Hello" + function TestFunction {$PrivateTest1} + }.Ast.FindAll({param($Ast) $Ast -is [Language.VariableExpressionAst]}, $true) | Select-Object -Last 1 )) + $res.Count | Should -Be 0 + } + + It 'Infers variable assigned with an attribute' { + $res = [AstTypeInference]::InferTypeOf( ({ + [ValidateNotNull()]$ValidatedVar1 = New-Guid + $ValidatedVar1 + }.Ast.FindAll({param($Ast) $Ast -is [Language.VariableExpressionAst]}, $true) | Select-Object -Last 1 )) + $res.Name | Should -Be 'System.Guid' + } + + It 'Infers variable assigned with multiple type constraints' { + $res = [AstTypeInference]::InferTypeOf( ({ + [int] [string]$MultiConstraintVar1 = "10" + $MultiConstraintVar1 + }.Ast.FindAll({param($Ast) $Ast -is [Language.VariableExpressionAst]}, $true) | Select-Object -Last 1 )) + $res.Name | Should -Be 'System.Int32' + } + + It 'Infers variable assigned by redirection' { + $res = [AstTypeInference]::InferTypeOf( ({ + New-Guid *>&1 1>variable:RedirVar1; $RedirVar1 + }.Ast.FindAll({param($Ast) $Ast -is [Language.VariableExpressionAst]}, $true) | Select-Object -Last 1 )) + $ExpectedTypeNames = @( + [ErrorRecord].FullName + [WarningRecord].FullName + [VerboseRecord].FullName + [DebugRecord].FullName + [InformationRecord].FullName + [guid].FullName + ) -join ';' + $res.Name -join ';' | Should -Be $ExpectedTypeNames + } + + It 'Infers variables assigned by redirection from specific streams' { + $VarAsts = [List[Language.Ast]]{ + [void](New-Guid 1>variable:RedirSuccess 2>variable:RedirError 3>variable:RedirWarning 4>variable:RedirVerbose 5>variable:RedirDebug 6>variable:RedirInfo) + $RedirSuccess + $RedirError + $RedirWarning + $RedirVerbose + $RedirDebug + $RedirInfo + }.Ast.FindAll({param($Ast) $Ast -is [Language.VariableExpressionAst]}, $true) + $ExpectedTypeNames = @( + [guid].FullName + [ErrorRecord].FullName + [WarningRecord].FullName + [VerboseRecord].FullName + [DebugRecord].FullName + [InformationRecord].FullName + ) + + for ($i = 0; $i -lt $VarAsts.Count; $i++) + { + $res = [AstTypeInference]::InferTypeOf($VarAsts[$i]) + $res.Name | Should -Be $ExpectedTypeNames[$i] + } + } + + It 'Should infer output from anonymous function' { + $res = [AstTypeInference]::InferTypeOf( { & {"Hello"} }.Ast) + $res.Name | Should -Be 'System.String' + } + + It 'Should infer output from function without OutputType attribute' { + function MyHello{"Hello"} + $res = [AstTypeInference]::InferTypeOf( { MyHello }.Ast) + $res.Name | Should -Be 'System.String' + } + + It 'Infers type of command with all streams redirected to Success stream' { + $res = [AstTypeInference]::InferTypeOf( { Get-PSDrive *>&1 }.Ast) + $ExpectedTypeNames = @( + [ErrorRecord].FullName + [WarningRecord].FullName + [VerboseRecord].FullName + [DebugRecord].FullName + [InformationRecord].FullName + [PSDriveInfo].FullName + ) -join ';' + $res.Name -join ';' | Should -Be $ExpectedTypeNames + } + + It 'Infers type of command with success stream redirected' { + $res = [AstTypeInference]::InferTypeOf( { Get-PSDrive *>&1 1>$null }.Ast) + $res.Count | Should -Be 0 + } + + It 'Infers type of command with some streams redirected to success' { + $res = [AstTypeInference]::InferTypeOf( { Get-PSDrive 3>&1 4>&1 }.Ast) + $res.Count | Should -Be 3 + $ExpectedTypeNames = @( + [PSDriveInfo].FullName + [VerboseRecord].FullName + [WarningRecord].FullName + ) -join ';' + ($res.Name | Sort-Object) -join ';' | Should -Be $ExpectedTypeNames + } + + It 'Infers type of command with other streams redirected to success' { + $res = [AstTypeInference]::InferTypeOf( { Get-PSDrive 2>&1 5>&1 6>&1 }.Ast) + $res.Count | Should -Be 4 + $ExpectedTypeNames = @( + [DebugRecord].FullName + [ErrorRecord].FullName + [InformationRecord].FullName + [PSDriveInfo].FullName + ) -join ';' + ($res.Name | Sort-Object) -join ';' | Should -Be $ExpectedTypeNames + } + + It 'Should only consider assignments wrapped in parentheses to be a part of the output in a Named block' { + $res = [AstTypeInference]::InferTypeOf( { [string]$Assignment1 = "Hello"; ([int]$Assignment2 = 42) }.Ast) + $res.Count | Should -Be 1 + $res.Name | Should -Be 'System.Int32' + } + + It 'Should only consider assignments wrapped in parentheses to be a part of the output in a Statement block' { + $res = [AstTypeInference]::InferTypeOf( { if ($true){ [string]$Assignment1 = "Hello"; ([int]$Assignment2 = 42) }}.Ast) + $res.Count | Should -Be 1 + $res.Name | Should -Be 'System.Int32' + } + + It 'Should only consider increments/decrements wrapped in parentheses to be a part of the output in a Named block' { + $res = [AstTypeInference]::InferTypeOf( { + [Int16]$Int16 = 1; [Int32]$Int32 = 1; [Int64]$Int64 = 1; [System.Int128]$Int128 = 1; + + $Int16++; $Int32--; ++$Int64; --$Int128}.Ast) + $res.Count | Should -Be 0 + + $res = [AstTypeInference]::InferTypeOf( { + [UInt16]$Uint16 = 1; [UInt32]$Uint32 = 1; [UInt64]$Uint64 = 1; [System.UInt128]$Uint128 = 1 + + ($Uint16++); ($Uint32--); (++$Uint64); (--$Uint128) }.Ast) + $res.Count | Should -Be 4 + $res.Name -join ',' | Should -Be ('System.UInt16', 'System.UInt32', 'System.UInt64', 'System.UInt128' -join ',') + } + + It 'Should only consider increments/decrements wrapped in parentheses to be a part of the output in a Statement block' { + $res = [AstTypeInference]::InferTypeOf( {if ($true){ + [Int16]$Int16 = 1; [Int32]$Int32 = 1; [Int64]$Int64 = 1; [System.Int128]$Int128 = 1; + + $Int16++; $Int32--; ++$Int64; --$Int128}}.Ast) + $res.Count | Should -Be 0 + + $res = [AstTypeInference]::InferTypeOf( {if ($true){ + [UInt16]$Uint16 = 1; [UInt32]$Uint32 = 1; [UInt64]$Uint64 = 1; [System.UInt128]$Uint128 = 1 + + ($Uint16++); ($Uint32--); (++$Uint64); (--$Uint128) }}.Ast) + $res.Count | Should -Be 4 + $res.Name -join ',' | Should -Be ('System.UInt16', 'System.UInt32', 'System.UInt64', 'System.UInt128' -join ',') + } + + It 'Redirected increments/decrements should be considered part of the output in a Named block' { + $res = [AstTypeInference]::InferTypeOf( { + [Int16]$Int16 = 1; [Int32]$Int32 = 1; [Int64]$Int64 = 1; [System.Int128]$Int128 = 1; + + $Int16++ *>&1; $Int32-- *>&1; ++$Int64 *>&1; --$Int128 *>&1}.Ast) + $res.Count | Should -Be 4 + $res.Name -join ',' | Should -Be ('System.Int16', 'System.Int32', 'System.Int64', 'System.Int128' -join ',') + } + + It 'Redirected increments/decrements should be considered part of the output in a Statement block' { + $res = [AstTypeInference]::InferTypeOf( {if ($true){ + [Int16]$Int16 = 1; [Int32]$Int32 = 1; [Int64]$Int64 = 1; [System.Int128]$Int128 = 1; + + $Int16++ *>&1; $Int32-- *>&1; ++$Int64 *>&1; --$Int128 *>&1}}.Ast) + $res.Count | Should -Be 4 + $res.Name -join ',' | Should -Be ('System.Int16', 'System.Int32', 'System.Int64', 'System.Int128' -join ',') + } } Describe "AstTypeInference tests" -Tags CI { 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 e9941d2b1f4..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("net9.0")] - [string] $BuildFramework = "net9.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 b3d5b83a79e..ba216dee23e 100644 --- a/test/tools/NamedPipeConnection/src/code/Microsoft.PowerShell.NamedPipeConnection.csproj +++ b/test/tools/NamedPipeConnection/src/code/Microsoft.PowerShell.NamedPipeConnection.csproj @@ -8,13 +8,13 @@ <AssemblyVersion>1.0.0.0</AssemblyVersion> <FileVersion>1.0.0</FileVersion> <InformationalVersion>1.0.0</InformationalVersion> - <TargetFrameworks>net9.0</TargetFrameworks> + <TargetFrameworks>net11.0</TargetFrameworks> <SuppressNETCoreSdkPreviewMessage>true</SuppressNETCoreSdkPreviewMessage> - <LangVersion>11.0</LangVersion> + <LangVersion>preview</LangVersion> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.CSharp" Version="4.7.0" /> - <PackageReference Include="System.Management.Automation" Version="7.5.0-preview.5" /> + <PackageReference Include="System.Management.Automation" Version="7.6.0-preview.2" /> </ItemGroup> </Project> diff --git a/test/tools/OpenCover/OpenCover.psm1 b/test/tools/OpenCover/OpenCover.psm1 index 6f2b3bdde4e..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/net9.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 00e4ba3e4b3..37b73426f6c 100644 --- a/test/tools/TestService/TestService.csproj +++ b/test/tools/TestService/TestService.csproj @@ -15,9 +15,8 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.Windows.Compatibility" Version="9.0.0" /> - <PackageReference Include="System.Formats.Asn1" Version="9.0.0" /> - <PackageReference Include="System.Data.SqlClient" Version="4.8.6" /> + <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/Controllers/GetController.cs b/test/tools/WebListener/Controllers/GetController.cs index 631886bb7e1..af563d16c2a 100644 --- a/test/tools/WebListener/Controllers/GetController.cs +++ b/test/tools/WebListener/Controllers/GetController.cs @@ -17,13 +17,13 @@ public JsonResult Index() Hashtable args = new Hashtable(); foreach (var key in Request.Query.Keys) { - args.Add(key, string.Join(Constants.HeaderSeparator, Request.Query[key])); + args.Add(key, string.Join(Constants.HeaderSeparator, (string)Request.Query[key])); } Hashtable headers = new Hashtable(); foreach (var key in Request.Headers.Keys) { - headers.Add(key, string.Join(Constants.HeaderSeparator, Request.Headers[key])); + headers.Add(key, string.Join(Constants.HeaderSeparator, (string)Request.Headers[key])); } Hashtable output = new Hashtable diff --git a/test/tools/WebListener/Controllers/MultipartController.cs b/test/tools/WebListener/Controllers/MultipartController.cs index 56e8c2003d4..5f053597791 100644 --- a/test/tools/WebListener/Controllers/MultipartController.cs +++ b/test/tools/WebListener/Controllers/MultipartController.cs @@ -75,7 +75,7 @@ public JsonResult Index(IFormCollection collection) Hashtable headers = new Hashtable(); foreach (var key in Request.Headers.Keys) { - headers.Add(key, string.Join(Constants.HeaderSeparator, Request.Headers[key])); + headers.Add(key, string.Join(Constants.HeaderSeparator, (string)Request.Headers[key])); } Hashtable output = new Hashtable diff --git a/test/tools/WebListener/Controllers/ResponseHeadersController.cs b/test/tools/WebListener/Controllers/ResponseHeadersController.cs index d5bffaefb70..f8693524714 100644 --- a/test/tools/WebListener/Controllers/ResponseHeadersController.cs +++ b/test/tools/WebListener/Controllers/ResponseHeadersController.cs @@ -23,7 +23,7 @@ public string Index() Hashtable headers = new Hashtable(); foreach (var key in Request.Query.Keys) { - headers.Add(key, string.Join(Constants.HeaderSeparator, Request.Query[key])); + headers.Add(key, string.Join(Constants.HeaderSeparator, (string)Request.Query[key])); if (string.Equals("Content-Type", key, StringComparison.InvariantCultureIgnoreCase)) { 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 d037627b1ff..3567cd93c68 100644 --- a/test/tools/WebListener/WebListener.csproj +++ b/test/tools/WebListener/WebListener.csproj @@ -7,7 +7,6 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="9.0.0" /> - <PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.0" /> + <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 new file mode 100644 index 00000000000..c428213a8e6 --- /dev/null +++ b/test/xUnit/csharp/test_CompletionHelpers.cs @@ -0,0 +1,183 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System; +using System.Management.Automation; +using Xunit; + +namespace PSTests.Parallel +{ + public class CompletionHelpersTests + { + [Theory] + [InlineData("", "'", "''")] + [InlineData("", "\"", "\"\"")] + [InlineData("'", "'", "''''")] + [InlineData("", "", "''")] + [InlineData("", null, "''")] + [InlineData("word", "'", "'word'")] + [InlineData("word", "\"", "\"word\"")] + [InlineData("word's", "'", "'word''s'")] + [InlineData("already 'quoted'", "'", "'already ''quoted'''")] + [InlineData("multiple 'quotes' in 'text'", "'", "'multiple ''quotes'' in ''text'''")] + [InlineData("\"word\"", "'", "'\"word\"'")] + [InlineData("'word'", "'", "''word''")] + [InlineData("word", "", "word")] + [InlineData("word", null, "word")] + [InlineData("\"word\"", "\"", "\"\"word\"\"")] + [InlineData("'word'", "\"", "\"'word'\"")] + [InlineData("word with space", "'", "'word with space'")] + [InlineData("word with space", "\"", "\"word with space\"")] + [InlineData("word\"with\"quotes", "'", "'word\"with\"quotes'")] + [InlineData("word'with'quotes", "\"", "\"word'with'quotes\"")] + [InlineData("while", "'", "'while'")] + [InlineData("while", "", "while")] + [InlineData("while", "\"", "\"while\"")] + [InlineData("$variable", "'", "'$variable'")] + [InlineData("$variable", "", "$variable")] + [InlineData("$variable", "\"", "\"$variable\"")] + [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, + string expected) + { + string result = CompletionHelpers.QuoteCompletionText(completionText, quote); + Assert.Equal(expected, result); + } + + [Theory] + [InlineData("normaltext", false)] + [InlineData("$variable", false)] + [InlineData("abc def", true)] + [InlineData("while", false)] // PowerShell keyword + [InlineData("key$word", true)] + [InlineData("abc`def", true)] + [InlineData("normal-text", false)] + [InlineData("\"doublequotes\"", false)] + [InlineData("'singlequotes'", false)] + [InlineData("normal 'text'", true)] + [InlineData("normal \"text\"", true)] + [InlineData("text with ' and \"", true)] + [InlineData("text\"with\"quotes", false)] + [InlineData("text'with'quotes", false)] + [InlineData("\"key$", true)] + [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); + Assert.Equal(expected, result); + } + + [Theory] + [InlineData("", "", "")] + [InlineData("\"", "", "\"")] + [InlineData("'", "", "'")] + [InlineData("\"word\"", "word", "\"")] + [InlineData("'word'", "word", "'")] + [InlineData("\"word", "word", "\"")] + [InlineData("'word", "word", "'")] + [InlineData("word\"", "word\"", "")] + [InlineData("word'", "word'", "")] + [InlineData("\"word's\"", "word's", "\"")] + [InlineData("'word\"", "'word\"", "")] + [InlineData("\"word'", "\"word'", "")] + [InlineData("'word\"s'", "word\"s", "'")] + public void TestHandleDoubleAndSingleQuote(string wordToComplete, string expectedWordToComplete, string expectedQuote) + { + string quote = CompletionHelpers.HandleDoubleAndSingleQuote(ref wordToComplete); + 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_Feedback.cs b/test/xUnit/csharp/test_Feedback.cs index 2c90118be55..eba627f7b0d 100644 --- a/test/xUnit/csharp/test_Feedback.cs +++ b/test/xUnit/csharp/test_Feedback.cs @@ -97,7 +97,7 @@ public static void GetFeedback() // Test the result from the 'general' feedback provider. Assert.Single(feedbacks); - Assert.Equal("general", feedbacks[0].Name); + Assert.Equal("General Feedback", feedbacks[0].Name); Assert.Equal(expectedCmd, feedbacks[0].Item.RecommendedActions[0]); // Expect the result from both 'general' and the 'slow' feedback providers. @@ -107,7 +107,7 @@ public static void GetFeedback() Assert.Equal(2, feedbacks.Count); FeedbackResult entry1 = feedbacks[0]; - Assert.Equal("general", entry1.Name); + Assert.Equal("General Feedback", entry1.Name); Assert.Equal(expectedCmd, entry1.Item.RecommendedActions[0]); FeedbackResult entry2 = feedbacks[1]; 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/csharp/test_Utils.cs b/test/xUnit/csharp/test_Utils.cs index bf562f3a8a4..0c45e06dcaf 100644 --- a/test/xUnit/csharp/test_Utils.cs +++ b/test/xUnit/csharp/test_Utils.cs @@ -163,5 +163,17 @@ public static void TestConvertToJsonCancellation() string json = JsonObject.ConvertToJson(hash, in context); Assert.Null(json); } + + [Fact] + public static void TestEmptyErrorRecordToString() + { + Assert.Equal(string.Empty, new ErrorRecord(new Exception(string.Empty), null, ErrorCategory.NotSpecified, null).ToString()); + } + + [Fact] + public static void TestNonEmptyErrorRecordToString() + { + Assert.Equal("test", new ErrorRecord(new Exception("test"), null, ErrorCategory.NotSpecified, null).ToString()); + } } } diff --git a/test/xUnit/csharp/test_WildcardPattern.cs b/test/xUnit/csharp/test_WildcardPattern.cs index 07f4045ef11..7aa4fc8c259 100644 --- a/test/xUnit/csharp/test_WildcardPattern.cs +++ b/test/xUnit/csharp/test_WildcardPattern.cs @@ -25,6 +25,22 @@ public void TestEscape_Empty() [InlineData("a", "a")] [InlineData("a*", "a`*")] [InlineData("*?[]", "`*`?`[`]")] + [InlineData("`", "``")] + [InlineData("```", "``````")] + [InlineData("a`", "a``")] + [InlineData("abc`def", "abc``def")] + [InlineData("abc``def", "abc````def")] + [InlineData("text with `backticks", "text with ``backticks")] + [InlineData("ends with `", "ends with ``")] + [InlineData("`starts with", "``starts with")] + [InlineData("`in`between`", "``in``between``")] + [InlineData("no special characters", "no special characters")] + [InlineData("`*`", "```*``")] + [InlineData("*`?[]", "`*```?`[`]")] + [InlineData("`*`?`[", "```*```?```[")] + [InlineData("*?[]`", "`*`?`[`]``")] + [InlineData("nested `backticks `inside`", "nested ``backticks ``inside``")] + [InlineData("wildcard*with`backtick", "wildcard`*with``backtick")] public void TestEscape_String(string source, string expected) { Assert.Equal(WildcardPattern.Escape(source), expected); diff --git a/test/xUnit/xUnit.tests.csproj b/test/xUnit/xUnit.tests.csproj index f0827571094..a2a9fe041e9 100644 --- a/test/xUnit/xUnit.tests.csproj +++ b/test/xUnit/xUnit.tests.csproj @@ -23,14 +23,14 @@ </ItemGroup> <ItemGroup> - <PackageReference Include="xunit" Version="2.9.2" /> - <PackageReference Include="Xunit.SkippableFact" Version="1.4.13" /> - <PackageReference Include="xunit.runner.visualstudio" Version="2.8.2"> + <PackageReference Include="xunit" Version="2.9.3" /> + <PackageReference Include="Xunit.SkippableFact" Version="1.5.61" /> + <PackageReference Include="xunit.runner.visualstudio" Version="3.1.5"> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <PrivateAssets>all</PrivateAssets> </PackageReference> - <PackageReference Include="XunitXml.TestLogger" Version="4.1.0" /> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" /> + <PackageReference Include="XunitXml.TestLogger" Version="8.0.0" /> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.4.0" /> </ItemGroup> <ItemGroup> diff --git a/tools/AttackSurfaceAnalyzer/README.md b/tools/AttackSurfaceAnalyzer/README.md new file mode 100644 index 00000000000..f57bb21f8c4 --- /dev/null +++ b/tools/AttackSurfaceAnalyzer/README.md @@ -0,0 +1,249 @@ +# Attack Surface Analyzer Testing + +This directory contains tools for running Attack Surface Analyzer (ASA) tests on PowerShell MSI installations using Docker. + +## Overview + +Attack Surface Analyzer is a Microsoft tool that helps analyze changes to a system's attack surface. These scripts allow you to run ASA tests locally in a clean Windows container to analyze what changes when PowerShell is installed. + +## Files + +- **Run-AttackSurfaceAnalyzer.ps1** - PowerShell script to run ASA tests with official MSIs +- **Summarize-AsaResults.ps1** - PowerShell script to analyze and summarize ASA results +- **docker/Dockerfile** - Multi-stage Dockerfile for building a container image with ASA pre-installed +- **README.md** - This documentation file + +## Docker Architecture + +The Docker implementation uses a multi-stage build to optimize the testing and result extraction process: + +### Multi-Stage Build Stages + +1. **asa-runner**: Main execution environment + - Base: `mcr.microsoft.com/dotnet/sdk:9.0-windowsservercore-ltsc2022` + - Contains Attack Surface Analyzer CLI tools + - Runs the complete test workflow + - Generates reports in both `C:\work` and `C:\reports` directories + +1. **asa-reports**: Minimal results layer + - Base: `mcr.microsoft.com/windows/nanoserver:ltsc2022` + - Contains only the test reports from the runner stage + - Enables clean extraction of results without container internals + +1. **final**: Default stage (inherits from asa-runner) + - Provides backward compatibility + - Used when no specific build target is specified + +### Benefits + +- **Clean Result Extraction**: Reports are isolated in a dedicated layer +- **Efficient Transfer**: Only test results are copied, not the entire container filesystem +- **Fallback Support**: Script includes fallback to volume-based extraction if needed +- **Minimal Footprint**: Final results layer contains only the necessary output files + +## Prerequisites + +- Windows 10/11 or Windows Server +- Docker Desktop with Windows containers enabled +- PowerShell 5.1 or later +- **An official signed PowerShell MSI file** from a released build + +### MSI Requirements + +**Important:** This tool now requires an official, digitally signed PowerShell MSI from Microsoft releases: + +- **Must be signed** by Microsoft Corporation +- **Must be from an official release** (downloaded from [PowerShell Releases](https://github.com/PowerShell/PowerShell/releases)) +- **Local builds are not supported** - unsigned or development MSIs will be rejected +- The script automatically verifies the digital signature before proceeding + +**Where to get official MSIs:** + +- Download from: https://github.com/PowerShell/PowerShell/releases +- Look for files like: `PowerShell-7.x.x-win-x64.msi` + +## Quick Start + +### Option 1: Using the PowerShell Script (Recommended) + +The script requires an official signed PowerShell MSI file: + +```powershell +# Run ASA test with official MSI (MsiPath is required) +.\tools\AttackSurfaceAnalyzer\Run-AttackSurfaceAnalyzer.ps1 -MsiPath "C:\path\to\PowerShell-7.4.0-win-x64.msi" + +# Specify custom output directory for results +.\tools\AttackSurfaceAnalyzer\Run-AttackSurfaceAnalyzer.ps1 -MsiPath ".\PowerShell-7.4.0-win-x64.msi" -OutputPath "C:\asa-results" + +# Keep the temporary work directory for debugging +.\tools\AttackSurfaceAnalyzer\Run-AttackSurfaceAnalyzer.ps1 -MsiPath ".\PowerShell-7.4.0-win-x64.msi" -KeepWorkDirectory +``` + +The script will: + +1. **Verify MSI signature** - Ensures the MSI is officially signed by Microsoft Corporation +1. Create a temporary work directory +1. Build a custom Docker container from the static Dockerfile +1. Start the Windows container with Attack Surface Analyzer +1. Take a baseline snapshot +1. Install the PowerShell MSI +1. Take a post-installation snapshot +1. Export comparison results +1. Copy results back to your specified output directory + +**Security Note:** The script will reject any MSI that is not digitally signed by Microsoft Corporation to ensure analysis is performed only on official releases. + +### Option 2: Using the Dockerfile + +If you prefer to build and use the container image directly: + +```powershell +# Build the Docker image (Dockerfile is in docker subfolder with clean context) +docker build -f tools\AttackSurfaceAnalyzer\docker\Dockerfile -t powershell-asa-test tools\AttackSurfaceAnalyzer\docker\ + +# Run the container with your MSI (script is built into the container) +docker run --rm --isolation process ` + -v "C:\path\to\msi\directory:C:\work" ` + powershell-asa-test +``` + +## Output Files + +The test will generate output files in the `./asa-results/` directory (or your specified `-OutputPath`): + +- **`asa.sqlite`** - SQLite database with full analysis data (primary result file) +- **`install.log`** - MSI installation log file +- **`*_summary.json.txt`** - Summary of detected changes (if generated) +- **`*_results.json.txt`** - Detailed results in JSON format (if generated) +- **`*.sarif`** - SARIF format results (if generated, can be viewed in VS Code) + +## Analyzing Results + +### Using the Summary Script (Recommended) + +Use the included summary script to get a comprehensive analysis: + +```powershell +# Basic summary of ASA results +.\tools\AttackSurfaceAnalyzer\Summarize-AsaResults.ps1 + +# Detailed analysis with rule breakdowns +.\tools\AttackSurfaceAnalyzer\Summarize-AsaResults.ps1 -ShowDetails + +# Analyze results from a specific location +.\tools\AttackSurfaceAnalyzer\Summarize-AsaResults.ps1 -Path "C:\custom\path\asa-results.json" -ShowDetails +``` + +The summary script provides: + +- **Overall statistics** - Total findings, analysis levels, category breakdowns +- **Rule analysis** - Which security rules were triggered and how often +- **File analysis** - Detailed breakdown of file-related security issues by rule type +- **Category cross-reference** - Shows which rules affect which categories + +### Using VS Code + +The SARIF files can be opened directly in VS Code with the SARIF Viewer extension to see a formatted view of the findings. + +### Using PowerShell + +```powershell +# Read the JSON results directly +$results = Get-Content "asa-results\asa-results.json" | ConvertFrom-Json +$results.Results.FILE_CREATED.Count # Number of files created + +# Query the SQLite database (requires SQLite tools) +# Example: List all file changes +# sqlite3 asa.sqlite "SELECT * FROM file_system WHERE change_type != 'NONE'" +``` + +## Troubleshooting + +### Docker Not Available + +The script automatically handles Docker Desktop installation and startup: + +**If Docker Desktop is installed but not running:** + +- The script will automatically start Docker Desktop for you +- It waits up to 60 seconds for Docker to become available +- You'll be prompted for confirmation (supports `-Confirm` and `-WhatIf`) + +**If Docker Desktop is not installed:** + +- The script will prompt you to install it automatically using winget +- After installation completes, start Docker Desktop and run the script again + +**Manual Installation:** + +1. Install Docker Desktop from https://www.docker.com/products/docker-desktop +1. Ensure Docker is running +1. Switch to Windows containers (right-click Docker tray icon → "Switch to Windows containers") + +### Container Fails to Start + +- Ensure you have enough disk space (containers can be large) +- Check that Windows containers are enabled in Docker settings +- Try pulling the base image manually: `docker pull mcr.microsoft.com/dotnet/sdk:9.0-windowsservercore-ltsc2022` + +### MSI Signature Verification Fails + +If you get signature verification errors: + +- **Ensure you're using an official MSI** from [PowerShell Releases](https://github.com/PowerShell/PowerShell/releases) +- **Do not use local builds** - only signed release MSIs are supported +- **Check certificate validity** - very old MSIs may have expired certificates +- **Verify file integrity** - redownload the MSI if it may be corrupted + +### No Results Generated + +- Check the install.log file for MSI installation errors +- Run with `-KeepWorkDirectory` to inspect the temporary work directory +- Verify the MSI file is valid and not corrupted + +## Advanced Usage + +### Parameters + +The `Run-AttackSurfaceAnalyzer.ps1` script supports these parameters: + +- **`-MsiPath`** (Required) - Path to the official signed PowerShell MSI file +- **`-OutputPath`** (Optional) - Directory for results (defaults to `./asa-results`) +- **`-ContainerImage`** (Optional) - Custom container base image +- **`-KeepWorkDirectory`** (Optional) - Keep temp directory for debugging + +Example with custom container image: + +```powershell +.\tools\AttackSurfaceAnalyzer\Run-AttackSurfaceAnalyzer.ps1 ` + -MsiPath ".\PowerShell-7.4.0-win-x64.msi" ` + -ContainerImage "mcr.microsoft.com/dotnet/sdk:8.0-windowsservercore-ltsc2022" +``` + +### Debugging + +To debug issues, keep the work directory and examine the files: + +```powershell +.\tools\AttackSurfaceAnalyzer\Run-AttackSurfaceAnalyzer.ps1 -KeepWorkDirectory + +# The script will print the work directory path +# You can then examine: +# - run-asa.ps1 - The script that runs in the container +# - install.log - MSI installation log +# - Any other generated files +``` + +## Integration with CI/CD + +These tools were extracted from the GitHub Actions workflow to allow local testing. If you need to integrate ASA testing back into a CI/CD pipeline, you can: + +1. Use the PowerShell script directly in your pipeline +1. Build and push the Docker image to a registry +1. Use the Dockerfile as a base for custom testing scenarios + +## More Information + +- [Attack Surface Analyzer on GitHub](https://github.com/microsoft/AttackSurfaceAnalyzer) +- [Docker for Windows Documentation](https://docs.docker.com/desktop/windows/) +- [SARIF Documentation](https://sarifweb.azurewebsites.net/) diff --git a/tools/AttackSurfaceAnalyzer/Run-AttackSurfaceAnalyzer.ps1 b/tools/AttackSurfaceAnalyzer/Run-AttackSurfaceAnalyzer.ps1 new file mode 100644 index 00000000000..2f7e502bff6 --- /dev/null +++ b/tools/AttackSurfaceAnalyzer/Run-AttackSurfaceAnalyzer.ps1 @@ -0,0 +1,590 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +<# +.SYNOPSIS + Run Attack Surface Analyzer test locally using Docker to analyze PowerShell MSI installation. + +.DESCRIPTION + This script runs Attack Surface Analyzer in a clean Windows container to analyze + the attack surface changes when installing PowerShell MSI. It takes a baseline + snapshot, installs the MSI, takes a post-installation snapshot, and exports the + comparison results. + +.PARAMETER MsiPath + Path to the official signed PowerShell MSI file to test. This must be a released, + signed MSI from the official PowerShell releases. + +.PARAMETER OutputPath + Directory where results will be saved. Defaults to './asa-results' subdirectory. + +.PARAMETER ContainerImage + Docker container image to use. Defaults to mcr.microsoft.com/dotnet/sdk:9.0-windowsservercore-ltsc2022 + +.PARAMETER KeepWorkDirectory + If specified, keeps the temporary work directory after the test completes. + +.EXAMPLE + .\Run-AttackSurfaceAnalyzer.ps1 -MsiPath "C:\path\to\PowerShell-7.4.0-win-x64.msi" + +.EXAMPLE + .\Run-AttackSurfaceAnalyzer.ps1 -MsiPath ".\PowerShell-7.4.0-win-x64.msi" -OutputPath "C:\asa-results" + +.NOTES + Requires Docker Desktop with Windows containers enabled. + Requires an official signed PowerShell MSI file from a released build. + + Docker Desktop Handling: + - If Docker Desktop is installed but not running, the script will start it automatically + - If Docker Desktop is not installed, the script will prompt to install it using winget + - Waits up to 60 seconds for Docker to become available after starting + + MSI Requirements: + - The MSI must be digitally signed by Microsoft Corporation + - The MSI must be from an official PowerShell release + - Local builds or unsigned MSIs are not supported + + Supports -WhatIf and -Confirm for Docker installation and startup. +#> + +[CmdletBinding(SupportsShouldProcess)] +param( + [Parameter(Mandatory)] + [string]$MsiPath, + + [Parameter()] + [string]$OutputPath = (Join-Path $PWD "asa-results"), + + [Parameter()] + [string]$ContainerImage = "mcr.microsoft.com/dotnet/sdk:9.0-windowsservercore-ltsc2022", + + [Parameter()] + [switch]$KeepWorkDirectory +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +function Write-Log { + param([string]$Message, [string]$Level = "INFO") + $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss" + $color = switch ($Level) { + "ERROR" { "Red" } + "WARNING" { "Yellow" } + "SUCCESS" { "Green" } + default { "White" } + } + Write-Host "[$timestamp] [$Level] $Message" -ForegroundColor $color +} + +function Test-MsiSignature { + param( + [Parameter(Mandatory)] + [string]$MsiPath + ) + + Write-Log "Verifying MSI signature..." -Level INFO + + try { + # Get the digital signature information + $signature = Get-AuthenticodeSignature -FilePath $MsiPath + + if ($signature.Status -ne 'Valid') { + Write-Log "MSI signature is not valid. Status: $($signature.Status)" -Level ERROR + return $false + } + + # Check if signed by Microsoft Corporation + $signerCertificate = $signature.SignerCertificate + if (-not $signerCertificate) { + Write-Log "No signer certificate found" -Level ERROR + return $false + } + + $subject = $signerCertificate.Subject + Write-Log "Certificate subject: $subject" -Level INFO + + # Check for Microsoft Corporation in the subject + if ($subject -notmatch "Microsoft Corporation" -and $subject -notmatch "CN=Microsoft Corporation") { + Write-Log "MSI is not signed by Microsoft Corporation" -Level ERROR + Write-Log "Expected: Microsoft Corporation" -Level ERROR + Write-Log "Found: $subject" -Level ERROR + return $false + } + + # Check certificate validity + $validFrom = $signerCertificate.NotBefore + $validTo = $signerCertificate.NotAfter + $now = Get-Date + + if ($now -lt $validFrom -or $now -gt $validTo) { + Write-Log "Certificate is not valid for current date" -Level ERROR + Write-Log "Valid from: $validFrom to: $validTo" -Level ERROR + return $false + } + + Write-Log "MSI signature verification passed" -Level SUCCESS + Write-Log "Signed by: $($signerCertificate.Subject)" -Level SUCCESS + Write-Log "Valid from: $validFrom to: $validTo" -Level SUCCESS + + return $true + } + catch { + Write-Log "Error verifying MSI signature: $_" -Level ERROR + return $false + } +} + +function Test-DockerAvailable { + try { + $null = docker version 2>&1 + return $true + } + catch { + return $false + } +} + +function Test-DockerDesktopInstalled { + # Check if Docker Desktop executable exists + $dockerDesktopPaths = @( + "${env:ProgramFiles}\Docker\Docker\Docker Desktop.exe", + "${env:ProgramFiles(x86)}\Docker\Docker\Docker Desktop.exe", + "${env:LOCALAPPDATA}\Programs\Docker\Docker Desktop.exe" + ) + + foreach ($path in $dockerDesktopPaths) { + if (Test-Path $path) { + return $path + } + } + return $null +} + +function Test-DockerDesktopRunning { + $process = Get-Process -Name "Docker Desktop" -ErrorAction SilentlyContinue + return $null -ne $process +} + +function Start-DockerDesktopApp { + [CmdletBinding(SupportsShouldProcess)] + param() + + $dockerDesktopPath = Test-DockerDesktopInstalled + + if (-not $dockerDesktopPath) { + Write-Log "Docker Desktop executable not found." -Level ERROR + return $false + } + + if (Test-DockerDesktopRunning) { + Write-Log "Docker Desktop is already running." -Level SUCCESS + return $true + } + + if ($PSCmdlet.ShouldProcess("Docker Desktop", "Start application")) { + Write-Log "Starting Docker Desktop..." -Level SUCCESS + Write-Log "This may take a minute for Docker to fully start..." + + try { + Start-Process -FilePath $dockerDesktopPath -WindowStyle Hidden + + # Wait for Docker to become available (up to 60 seconds) + $maxWaitSeconds = 60 + $waitedSeconds = 0 + + while ($waitedSeconds -lt $maxWaitSeconds) { + Start-Sleep -Seconds 5 + $waitedSeconds += 5 + Write-Log "Waiting for Docker to start... ($waitedSeconds/$maxWaitSeconds seconds)" + + if (Test-DockerAvailable) { + Write-Log "Docker Desktop started successfully!" -Level SUCCESS + return $true + } + } + + Write-Log "Docker Desktop was started but is not responding yet. Please wait a moment and try again." -Level WARNING + return $false + } + catch { + Write-Log "Error starting Docker Desktop: $_" -Level ERROR + return $false + } + } + else { + Write-Log "Starting Docker Desktop cancelled by user." -Level WARNING + return $false + } +} + +function Test-WingetAvailable { + try { + $null = Get-Command winget -ErrorAction Stop + return $true + } + catch { + return $false + } +} + +function Install-DockerDesktop { + [CmdletBinding(SupportsShouldProcess, ConfirmImpact="High")] + param() + + if (-not (Test-WingetAvailable)) { + Write-Log "winget is not available. Please install winget (App Installer from Microsoft Store) or install Docker Desktop manually from https://www.docker.com/products/docker-desktop" -Level ERROR + return $false + } + + if ($PSCmdlet.ShouldProcess("Docker Desktop", "Install using winget")) { + Write-Log "Installing Docker Desktop using winget..." -Level SUCCESS + Write-Log "This may take several minutes..." + + try { + winget install docker.dockerdesktop --accept-package-agreements --accept-source-agreements + + if ($LASTEXITCODE -eq 0) { + Write-Log "Docker Desktop installed successfully!" -Level SUCCESS + Write-Log "Please restart Docker Desktop and ensure Windows containers are enabled, then run this script again." -Level SUCCESS + return $true + } + else { + Write-Log "Docker Desktop installation failed with exit code: $LASTEXITCODE" -Level ERROR + return $false + } + } + catch { + Write-Log "Error installing Docker Desktop: $_" -Level ERROR + return $false + } + } + else { + Write-Log "Docker Desktop installation cancelled by user." -Level WARNING + return $false + } +} + +# Verify Docker is available +Write-Log "Checking Docker availability..." +if (-not (Test-DockerAvailable)) { + Write-Log "Docker is not responding." -Level WARNING + + # Check if Docker Desktop is installed but not running + if (Test-DockerDesktopInstalled) { + Write-Log "Docker Desktop is installed but not running." -Level WARNING + + if (Start-DockerDesktopApp) { + Write-Log "Docker Desktop is now running and ready." -Level SUCCESS + } + else { + Write-Log "Failed to start Docker Desktop or it's taking longer than expected." -Level ERROR + Write-Log "Please start Docker Desktop manually and ensure Windows containers are enabled, then run this script again." -Level ERROR + exit 1 + } + } + else { + # Docker Desktop is not installed + Write-Log "Docker Desktop is not installed." -Level WARNING + Write-Log "Docker Desktop is required to run Attack Surface Analyzer tests in containers." -Level WARNING + + if (Install-DockerDesktop) { + Write-Log "Docker Desktop has been installed. Please restart Docker Desktop and run this script again." -Level SUCCESS + exit 0 + } + else { + Write-Log "Please install Docker Desktop manually from https://www.docker.com/products/docker-desktop and ensure it's running with Windows containers enabled." -Level ERROR + exit 1 + } + } +} + +# Verify MSI exists and is properly signed +if (-not (Test-Path $MsiPath)) { + Write-Log "MSI file not found: $MsiPath" -Level ERROR + exit 1 +} + +$MsiPath = Resolve-Path $MsiPath +Write-Log "Using MSI: $MsiPath" + +# Verify MSI signature +if (-not (Test-MsiSignature -MsiPath $MsiPath)) { + Write-Log "MSI signature verification failed. Only official signed PowerShell MSIs are supported." -Level ERROR + Write-Log "Please download an official PowerShell MSI from: https://github.com/PowerShell/PowerShell/releases" -Level ERROR + exit 1 +} + +# Create output directory +$OutputPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($OutputPath) +if (-not (Test-Path $OutputPath)) { + New-Item -ItemType Directory -Path $OutputPath -Force | Out-Null + Write-Log "Created output directory: $OutputPath" +} + +# Create container work directory +$containerWorkDir = Join-Path $env:TEMP "asa-container-work-$(Get-Date -Format 'yyyyMMdd-HHmmss')" +New-Item -ItemType Directory -Force -Path $containerWorkDir | Out-Null +Write-Log "Created container work directory: $containerWorkDir" + +try { + # Use the static Dockerfile from the docker subfolder + $dockerContextPath = Join-Path $PSScriptRoot "docker" + + # Copy MSI to Docker build context + $msiFileName = Split-Path $MsiPath -Leaf + $destMsiPath = Join-Path $dockerContextPath $msiFileName + Write-Log "Copying MSI to Docker build context..." + Copy-Item $MsiPath -Destination $destMsiPath + $staticDockerfilePath = Join-Path $dockerContextPath "Dockerfile" + Write-Log "Using static Dockerfile: $staticDockerfilePath" + + if (-not (Test-Path $staticDockerfilePath)) { + Write-Log "Static Dockerfile not found at: $staticDockerfilePath" -Level ERROR + exit 1 + } + + Write-Log "Docker build context: $dockerContextPath" + + # Build custom container image from static Dockerfile + Write-Log "=========================================" -Level SUCCESS + Write-Log "Building custom Attack Surface Analyzer container..." -Level SUCCESS + Write-Log "=========================================" -Level SUCCESS + + Write-Log "=========================================" -Level SUCCESS + Write-Log "Building ASA test container..." -Level SUCCESS + Write-Log "=========================================" -Level SUCCESS + Write-Log "This may take several minutes..." + + # Build the asa-reports stage specifically + $reportsImageName = "powershell-asa-reports:latest" + docker build --target asa-reports -t $reportsImageName -f $staticDockerfilePath $dockerContextPath + + if ($LASTEXITCODE -ne 0) { + Write-Log "Docker build failed with exit code: $LASTEXITCODE" -Level ERROR + exit 1 + } + + Write-Log "Build completed successfully" -Level SUCCESS + + # Extract reports from the built image + Write-Log "=========================================" -Level SUCCESS + Write-Log "Extracting reports to: $OutputPath" -Level SUCCESS + Write-Log "=========================================" -Level SUCCESS + + $tempContainerName = "asa-reports-extract-$(Get-Date -Format 'yyyyMMdd-HHmmss')" + + try { + # Create a container from the reports image (but don't run it) + docker create --name $tempContainerName $reportsImageName + + if ($LASTEXITCODE -ne 0) { + Write-Log "Failed to create temporary container for extraction" -Level ERROR + exit 1 + } + + # Try to extract known report file patterns individually + Write-Log "Extracting report files..." -Level INFO + + # Extract standardized report files directly (no file listing needed) + # Extract files with standardized names (no wildcards needed) + Write-Log "Extracting standardized report files..." -Level INFO + $reportFilePatterns = @( + "asa.sqlite", + "asa-results.json", + "install.log" + ) + + $extractedAny = $false + + foreach ($filename in $reportFilePatterns) { + try { + Write-Log "Trying to extract file: $filename" -Level INFO + docker cp "${tempContainerName}:/$filename" $OutputPath 2>$null + + if ($LASTEXITCODE -eq 0) { + Write-Log "Successfully extracted: $filename" -Level SUCCESS + $extractedAny = $true + } else { + Write-Log "File not found: $filename" -Level INFO + } + } + catch { + Write-Log "Error extracting file $filename : $_" -Level WARNING + } + } + + # Alternative approach: extract the entire reports directory if individual files don't work + if (-not $extractedAny) { + Write-Log "Trying to extract entire directory..." -Level INFO + docker cp "${tempContainerName}:/" "$OutputPath/reports" 2>$null + + if ($LASTEXITCODE -eq 0) { + Write-Log "Successfully extracted reports directory" -Level SUCCESS + $extractedAny = $true + } + } + + if ($extractedAny) { + Write-Log "Report extraction completed successfully" -Level SUCCESS + } else { + Write-Log "No reports could be extracted - this may be normal if no issues were found" -Level WARNING + } + } + finally { + # Clean up the temporary container + docker rm $tempContainerName -f 2>$null + } + + # Check what files were extracted + Write-Host "" + Write-Log "=========================================" -Level SUCCESS + Write-Log "Checking extracted results..." -Level SUCCESS + Write-Log "=========================================" -Level SUCCESS + + $resultFiles = Get-ChildItem -Path $OutputPath -ErrorAction SilentlyContinue + $copiedCount = $resultFiles.Count + + if ($copiedCount -eq 0) { + Write-Log "Warning: No result files found in extracted output" -Level WARNING + } + else { + Write-Log "Successfully extracted $copiedCount file(s):" -Level SUCCESS + $resultFiles | ForEach-Object { + if ($_.PSIsContainer) { + Write-Log " - $($_.Name) (directory)" -Level SUCCESS + } else { + Write-Log " - $($_.Name) ($([math]::Round($_.Length/1KB, 2)) KB)" -Level SUCCESS + } + } + } + + Write-Host "" + Write-Log "=========================================" -Level SUCCESS + Write-Log "Attack Surface Analyzer test completed!" -Level SUCCESS + Write-Log "=========================================" -Level SUCCESS + Write-Log "Results saved to: $OutputPath" -Level SUCCESS + + # Check for ASA GUI availability and launch interactive analysis + $dbPath = Join-Path $OutputPath "asa.sqlite" + $jsonPath = Join-Path $OutputPath "asa-results.json" + + if (Test-Path $dbPath) { + # Check if ASA CLI is available + $asaAvailable = $false + try { + $asaVersion = asa --version 2>$null + if ($LASTEXITCODE -eq 0) { + $asaAvailable = $true + Write-Log "Attack Surface Analyzer CLI detected: $($asaVersion.Trim())" -Level INFO + } + } + catch { + # ASA not available via PATH + } + + # Try dotnet tool global path if ASA not found in PATH + if (-not $asaAvailable) { + $globalToolsPath = "$env:USERPROFILE\.dotnet\tools\asa.exe" + if (Test-Path $globalToolsPath) { + try { + $asaVersion = & $globalToolsPath --version 2>$null + if ($LASTEXITCODE -eq 0) { + $asaAvailable = $true + Write-Log "Attack Surface Analyzer found in global tools: $($asaVersion.Trim())" -Level INFO + # Use full path for subsequent commands + $asaCommand = $globalToolsPath + } + } + catch { + # Global tools ASA not working + } + } + } else { + $asaCommand = "asa" + } + + if ($asaAvailable) { + Write-Log "Launching Attack Surface Analyzer GUI for interactive analysis..." -Level SUCCESS + try { + # Launch ASA GUI with the database file + $asaProcess = Start-Process -FilePath $asaCommand -ArgumentList "gui", "--databasefilename", "`"$dbPath`"" -PassThru -NoNewWindow:$false + + if ($asaProcess) { + Write-Log "ASA GUI launched successfully (PID: $($asaProcess.Id))" -Level SUCCESS + Write-Log "Interactive analysis interface is now available" -Level INFO + } else { + Write-Log "Failed to launch ASA GUI" -Level WARNING + } + } + catch { + Write-Log "Error launching ASA GUI: $_" -Level WARNING + Write-Log "You can manually launch the GUI with: asa gui --databasefilename `"$dbPath`"" -Level INFO + } + } else { + Write-Log "Attack Surface Analyzer CLI not found" -Level INFO + Write-Log "Install ASA globally to enable GUI analysis: dotnet tool install -g Microsoft.CST.AttackSurfaceAnalyzer.CLI" -Level INFO + Write-Log "Then launch GUI manually with: asa gui --databasefilename `"$dbPath`"" -Level INFO + } + } else { + Write-Log "Database file not found - cannot launch ASA GUI" -Level WARNING + } + + # Also check for VS Code integration for JSON analysis + if (Test-Path $jsonPath) { + # Detect if running in VS Code + $isVSCode = $false + + if ($env:VSCODE_PID -or $env:TERM_PROGRAM -eq "vscode" -or $env:VSCODE_INJECTION -eq "1") { + $isVSCode = $true + } + + # Check if 'code' command is available + if (-not $isVSCode) { + try { + $null = & code --version 2>$null + if ($LASTEXITCODE -eq 0) { + $isVSCode = $true + } + } + catch { + # 'code' command not available + } + } + + if ($isVSCode) { + Write-Log "VS Code detected - opening JSON results for analysis..." -Level INFO + try { + & code $jsonPath + if ($LASTEXITCODE -eq 0) { + Write-Log "JSON results file opened in VS Code: $jsonPath" -Level SUCCESS + } else { + Write-Log "Failed to open JSON file in VS Code" -Level WARNING + } + } + catch { + Write-Log "Error opening JSON file in VS Code: $_" -Level WARNING + } + } else { + Write-Log "JSON analysis results available at: $jsonPath" -Level INFO + Write-Log "Open this file in VS Code or any JSON viewer for detailed analysis" -Level INFO + } + } +} +finally { + # Cleanup + if (-not $KeepWorkDirectory) { + Write-Log "Cleaning up temporary work directory..." + Remove-Item -Path $containerWorkDir -Recurse -Force -ErrorAction SilentlyContinue + Write-Log "Cleanup completed" + } + else { + Write-Log "Work directory preserved at: $containerWorkDir" -Level SUCCESS + } + + # Always cleanup MSI file from Docker build context + if ($destMsiPath -and (Test-Path $destMsiPath)) { + Write-Log "Cleaning up MSI file from Docker context..." + Remove-Item -Path $destMsiPath -Force -ErrorAction SilentlyContinue + } +} diff --git a/tools/AttackSurfaceAnalyzer/Summarize-AsaResults.ps1 b/tools/AttackSurfaceAnalyzer/Summarize-AsaResults.ps1 new file mode 100644 index 00000000000..00f27014037 --- /dev/null +++ b/tools/AttackSurfaceAnalyzer/Summarize-AsaResults.ps1 @@ -0,0 +1,636 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +#Requires -Version 5.1 +<# +.SYNOPSIS + Summarizes Attack Surface Analyzer (ASA) results from a JSON file. + +.DESCRIPTION + This script analyzes ASA JSON results and provides a comprehensive summary of security findings, + including counts by category, analysis levels, and detailed breakdowns of security issues. + +.PARAMETER Path + Path to the ASA results JSON file. Defaults to 'asa-results\asa-results.json' in the current directory. + +.PARAMETER ShowDetails + Shows detailed information about each finding category. + +.PARAMETER IncludeInformationalEvent + Includes informational events in the analysis. By default, only WARNING and ERROR events are processed. + +.PARAMETER IncludeDebugEvent + Includes debug events in the analysis. By default, only WARNING and ERROR events are processed. + +.EXAMPLE + .\Summarize-AsaResults.ps1 + + Summarizes the ASA results with basic statistics, showing only WARNING and ERROR events. + +.EXAMPLE + .\Summarize-AsaResults.ps1 -ShowDetails + + Shows detailed breakdown of findings by category, filtering out informational and debug events. + +.EXAMPLE + .\Summarize-AsaResults.ps1 -IncludeInformationalEvent + + Includes informational events along with WARNING and ERROR events in the analysis..NOTES + Author: GitHub Copilot + Version: 1.0 + Created for PowerShell ASA Analysis +#> + +[CmdletBinding()] +param( + [Parameter()] + [string]$Path = "asa-results\asa-results.json", + + [Parameter()] + [switch]$ShowDetails, + + [Parameter()] + [switch]$IncludeInformationalEvent, + + [Parameter()] + [switch]$IncludeDebugEvent +) + +function Get-AsaSummary { + param( + [Parameter(Mandatory)] + $AsaData, + + [Parameter()] + [switch]$IncludeInformationalEvent, + + [Parameter()] + [switch]$IncludeDebugEvent + ) + + # Extract metadata + $metadata = $AsaData["Metadata"] + $results = $AsaData["Results"] + + # Initialize counters + $summary = @{ + Metadata = @{ + Version = $metadata["compare-version"] + OS = $metadata["compare-os"] + OSVersion = $metadata["compare-osversion"] + BaseRunId = "" + CompareRunId = "" + } + Categories = @{} + TotalFindings = 0 + AnalysisLevels = @{ + WARNING = 0 + ERROR = 0 + INFORMATION = 0 + DEBUG = 0 + } + RuleTypes = @{} + FileIssuesByRule = @{} + FileExtensionSummary = @{} + TimeSpan = $null + } + + # Process each category + foreach ($categoryName in $results.Keys) { + $categoryData = $results[$categoryName] + + $summary.Categories[$categoryName] = @{ + Count = 0 + Items = @() + } + + # Process items in category with filtering + foreach ($item in $categoryData) { + # Filter events based on analysis level + $analysisLevel = $item["Analysis"] + if ($analysisLevel) { + # Skip informational events unless explicitly included + if ($analysisLevel -eq "INFORMATION" -and -not $IncludeInformationalEvent) { + continue + } + # Skip debug events unless explicitly included + if ($analysisLevel -eq "DEBUG" -and -not $IncludeDebugEvent) { + continue + } + + $summary.AnalysisLevels[$analysisLevel]++ + } # If we reach here, the item passed the filter + $summary.Categories[$categoryName].Count++ + $summary.TotalFindings++ + + # Extract run IDs and calculate timespan + if ($item["BaseRunId"]) { + $summary.Metadata.BaseRunId = $item["BaseRunId"] + } + if ($item["CompareRunId"]) { + $summary.Metadata.CompareRunId = $item["CompareRunId"] + } + + # Process rules + foreach ($rule in $item["Rules"]) { + $ruleName = $rule["Name"] + if (-not $summary.RuleTypes.ContainsKey($ruleName)) { + $summary.RuleTypes[$ruleName] = @{ + Count = 0 + Description = $rule["Description"] + Flag = $rule["Flag"] + Platforms = $rule["Platforms"] + Categories = @{} + } + } + $summary.RuleTypes[$ruleName].Count++ + + # Track which categories this rule appears in + if (-not $summary.RuleTypes[$ruleName].Categories.ContainsKey($categoryName)) { + $summary.RuleTypes[$ruleName].Categories[$categoryName] = 0 + } + $summary.RuleTypes[$ruleName].Categories[$categoryName]++ + + # For file-related categories, track file extension if available + if ($categoryName -like "*FILE*" -and $item["Identity"]) { + $fileExtension = [System.IO.Path]::GetExtension($item["Identity"]).ToLower() + if (-not $fileExtension) { $fileExtension = "(no extension)" } + + # Track by rule and extension + if (-not $summary.FileIssuesByRule.ContainsKey($ruleName)) { + $summary.FileIssuesByRule[$ruleName] = @{} + } + if (-not $summary.FileIssuesByRule[$ruleName].ContainsKey($fileExtension)) { + $summary.FileIssuesByRule[$ruleName][$fileExtension] = 0 + } + $summary.FileIssuesByRule[$ruleName][$fileExtension]++ + + # Track overall file extension summary + if (-not $summary.FileExtensionSummary.ContainsKey($fileExtension)) { + $summary.FileExtensionSummary[$fileExtension] = @{ + Count = 0 + Rules = @{} + Categories = @{} + } + } + $summary.FileExtensionSummary[$fileExtension].Count++ + + # Track which rules affect this extension + if (-not $summary.FileExtensionSummary[$fileExtension].Rules.ContainsKey($ruleName)) { + $summary.FileExtensionSummary[$fileExtension].Rules[$ruleName] = 0 + } + $summary.FileExtensionSummary[$fileExtension].Rules[$ruleName]++ + + # Track which categories this extension appears in + if (-not $summary.FileExtensionSummary[$fileExtension].Categories.ContainsKey($categoryName)) { + $summary.FileExtensionSummary[$fileExtension].Categories[$categoryName] = 0 + } + $summary.FileExtensionSummary[$fileExtension].Categories[$categoryName]++ + } + } + + # Store item details for detailed view + $summary.Categories[$categoryName].Items += @{ + Identity = $item["Identity"] + Analysis = $item["Analysis"] + Rules = $item["Rules"] + Compare = $item["Compare"] + } + } + } + + # Calculate timespan if we have both run IDs + if ($summary.Metadata.BaseRunId -and $summary.Metadata.CompareRunId) { + try { + $baseTime = [DateTime]::Parse($summary.Metadata.BaseRunId) + $compareTime = [DateTime]::Parse($summary.Metadata.CompareRunId) + $summary.TimeSpan = $compareTime - $baseTime + } + catch { + $summary.TimeSpan = "Unable to calculate" + } + } + + return $summary +} + +function Write-ConsoleSummary { + param( + [Parameter(Mandatory)] + [hashtable]$Summary, + + [Parameter()] + [switch]$ShowDetails, + + [Parameter()] + [switch]$IncludeInformationalEvent, + + [Parameter()] + [switch]$IncludeDebugEvent + ) + + # Header + Write-Host ("=" * 80) -ForegroundColor Cyan + Write-Host "Attack Surface Analyzer Results Summary" -ForegroundColor Cyan + Write-Host ("=" * 80) -ForegroundColor Cyan + Write-Host "" + + # Metadata + Write-Host "Analysis Metadata:" -ForegroundColor Yellow + Write-Host " ASA Version: $($Summary.Metadata.Version)" -ForegroundColor White + Write-Host " Operating System: $($Summary.Metadata.OS) ($($Summary.Metadata.OSVersion))" -ForegroundColor White + if ($Summary.TimeSpan -and $Summary.TimeSpan -ne "Unable to calculate") { + Write-Host " Analysis Duration: $($Summary.TimeSpan.ToString())" -ForegroundColor White + } + Write-Host "" + + # Overall Statistics + Write-Host "Overall Statistics:" -ForegroundColor Yellow + Write-Host " Total Findings: $($Summary.TotalFindings)" -ForegroundColor White + + # Show filtering information + $filterInfo = @() + if (-not $IncludeInformationalEvent) { $filterInfo += "INFORMATION events excluded" } + if (-not $IncludeDebugEvent) { $filterInfo += "DEBUG events excluded" } + if ($filterInfo.Count -gt 0) { + Write-Host " Filtering: $($filterInfo -join ', ')" -ForegroundColor DarkYellow + } + + # Analysis Levels + Write-Host " Analysis Levels:" -ForegroundColor White + foreach ($level in $Summary.AnalysisLevels.Keys | Sort-Object) { + $count = $Summary.AnalysisLevels[$level] + $color = switch ($level) { + 'ERROR' { 'Red' } + 'WARNING' { 'Yellow' } + 'INFORMATION' { 'Green' } + 'DEBUG' { 'DarkGray' } + default { 'White' } + } + Write-Host " $level`: $count" -ForegroundColor $color + } + Write-Host "" + + # Category Breakdown + Write-Host "Findings by Category:" -ForegroundColor Yellow + $sortedCategories = $Summary.Categories.GetEnumerator() | Sort-Object { $_.Value.Count } -Descending + + foreach ($category in $sortedCategories) { + $categoryName = $category.Key + $count = $category.Value.Count + + if ($count -gt 0) { + Write-Host " $categoryName`: $count items" -ForegroundColor Cyan + } + else { + Write-Host " $categoryName`: $count items" -ForegroundColor DarkGray + } + } + Write-Host "" + + # Rule Types Summary + Write-Host "Top Security Rules Triggered:" -ForegroundColor Yellow + $topRules = $Summary.RuleTypes.GetEnumerator() | + Sort-Object { $_.Value.Count } -Descending | + Select-Object -First 10 + + foreach ($rule in $topRules) { + $ruleName = $rule.Key + $count = $rule.Value.Count + $flag = $rule.Value.Flag + + $color = switch ($flag) { + 'ERROR' { 'Red' } + 'WARNING' { 'Yellow' } + 'INFORMATION' { 'Green' } + 'DEBUG' { 'DarkGray' } + default { 'White' } + } + + Write-Host " [$flag] $ruleName`: $count occurrences" -ForegroundColor $color + if ($ShowDetails) { + Write-Host " Description: $($rule.Value.Description)" -ForegroundColor DarkGray + Write-Host " Platforms: $($rule.Value.Platforms -join ', ')" -ForegroundColor DarkGray + + # Show breakdown by category for this rule + if ($rule.Value.Categories.Count -gt 0) { + Write-Host " Categories:" -ForegroundColor DarkGray + foreach ($cat in $rule.Value.Categories.GetEnumerator() | Sort-Object { $_.Value } -Descending) { + Write-Host " $($cat.Key): $($cat.Value) occurrences" -ForegroundColor Gray + } + } + } + } + + # File Extension Summary + if ($Summary.FileExtensionSummary.Count -gt 0) { + Write-Host "" + Write-Host "File Extension Analysis:" -ForegroundColor Yellow + + $sortedExtensions = $Summary.FileExtensionSummary.GetEnumerator() | + Sort-Object { $_.Value.Count } -Descending | + Select-Object -First 15 + + foreach ($extEntry in $sortedExtensions) { + $extension = $extEntry.Key + $count = $extEntry.Value.Count + $displayExt = if ($extension -eq "(no extension)") { $extension } else { "*$extension" } + + Write-Host " $displayExt`: $count files" -ForegroundColor Cyan + + if ($ShowDetails) { + # Show top rules for this extension + $topRulesForExt = $extEntry.Value.Rules.GetEnumerator() | + Sort-Object { $_.Value } -Descending | + Select-Object -First 3 + + foreach ($ruleEntry in $topRulesForExt) { + $ruleName = $ruleEntry.Key + $ruleCount = $ruleEntry.Value + Write-Host " $ruleName`: $ruleCount files" -ForegroundColor Gray + } + } + } + } + + # Detailed Rule Analysis by Category + if ($ShowDetails) { + Write-Host "" + Write-Host "Detailed Rule Analysis by Category:" -ForegroundColor Yellow + + # Focus on file-related categories + $fileCategories = $Summary.Categories.GetEnumerator() | Where-Object { $_.Key -like "*FILE*" -and $_.Value.Count -gt 0 } + + foreach ($category in $fileCategories) { + $categoryName = $category.Key + Write-Host "" + Write-Host " $categoryName Rules Breakdown:" -ForegroundColor Cyan + + # Get rules that appear in this category + $categoryRules = $Summary.RuleTypes.GetEnumerator() | + Where-Object { $_.Value.Categories.ContainsKey($categoryName) } | + Sort-Object { $_.Value.Categories[$categoryName] } -Descending + + foreach ($ruleEntry in $categoryRules) { + $ruleName = $ruleEntry.Key + $count = $ruleEntry.Value.Categories[$categoryName] + $flag = $ruleEntry.Value.Flag + + $color = switch ($flag) { + 'ERROR' { 'Red' } + 'WARNING' { 'Yellow' } + 'INFORMATION' { 'Green' } + 'DEBUG' { 'DarkGray' } + default { 'White' } + } + + Write-Host " [$flag] $ruleName`: $count files" -ForegroundColor $color + } + } + + # Show file extension breakdown if available + if ($Summary.FileIssuesByRule.Count -gt 0) { + Write-Host "" + Write-Host "File Issues by Rule and Extension:" -ForegroundColor Yellow + + foreach ($ruleEntry in $Summary.FileIssuesByRule.GetEnumerator()) { + $ruleName = $ruleEntry.Key + Write-Host "" + Write-Host " $ruleName`:" -ForegroundColor Cyan + + $sortedExtensions = $ruleEntry.Value.GetEnumerator() | Sort-Object { $_.Value } -Descending + foreach ($extEntry in $sortedExtensions) { + $extension = $extEntry.Key + $count = $extEntry.Value + Write-Host " $extension`: $count files" -ForegroundColor White + } + } + } + } + + # Detailed Category Information + if ($ShowDetails) { + Write-Host "" + Write-Host "Detailed Category Breakdown:" -ForegroundColor Yellow + + foreach ($category in $sortedCategories | Where-Object { $_.Value.Count -gt 0 }) { + $categoryName = $category.Key + $items = $category.Value.Items + + Write-Host "" + Write-Host " $categoryName ($($items.Count) items):" -ForegroundColor Cyan + + # Group by analysis level + $groupedByAnalysis = $items | Group-Object Analysis + foreach ($group in $groupedByAnalysis) { + $level = $group.Name + $count = $group.Count + + $color = switch ($level) { + 'ERROR' { 'Red' } + 'WARNING' { 'Yellow' } + 'INFORMATION' { 'Green' } + 'DEBUG' { 'DarkGray' } + default { 'White' } + } + + Write-Host " $level`: $count items" -ForegroundColor $color + } + + # Show individual file details for file-related categories + if ($categoryName -like "*FILE*" -and $items.Count -gt 0) { + # Check if this category contains files with expired signatures + $expiredSigItems = $items | Where-Object { + $_.Rules -and ($_.Rules | Where-Object { $_.Name -eq 'Binaries with expired signatures' }) + } + + if ($expiredSigItems.Count -gt 0) { + Write-Host "" + Write-Host " Files with Expired Signatures (grouped by Issuer):" -ForegroundColor DarkCyan + + # Group by issuer only + $groupedByIssuer = @{} + foreach ($item in $expiredSigItems) { + if ($item.Compare -and $item.Compare.SignatureStatus -and $item.Compare.SignatureStatus.SigningCertificate) { + $cert = $item.Compare.SignatureStatus.SigningCertificate + $issuer = $cert.Issuer + $notAfter = $cert.NotAfter + $identity = $item.Identity + + if (-not $groupedByIssuer.ContainsKey($issuer)) { + $groupedByIssuer[$issuer] = @() + } + $groupedByIssuer[$issuer] += [PSCustomObject]@{ + Identity = $identity + NotAfter = $notAfter + } + } + } + + # Display grouped results + $sortedIssuers = $groupedByIssuer.GetEnumerator() | Sort-Object Name + + foreach ($issuerGroup in $sortedIssuers) { + $issuer = $issuerGroup.Name + $files = $issuerGroup.Value + $fileCount = $files.Count + + Write-Host "" + Write-Host " Issuer: $issuer" -ForegroundColor Yellow + Write-Host " Files ($fileCount):" -ForegroundColor White + + # Sort files by full file path + $sortedFiles = $files | Sort-Object Identity + + # Show all files + foreach ($file in $sortedFiles) { + # Get identity - handle both hashtable and PSCustomObject + $filePath = if ($file -is [hashtable]) { $file['Identity'] } else { $file.Identity } + + # Format date without time + $expirationDate = 'Unknown' + $notAfterValue = if ($file -is [hashtable]) { $file['NotAfter'] } else { $file.NotAfter } + if ($notAfterValue) { + try { + $expirationDate = ([DateTime]::Parse($notAfterValue)).ToString('yyyy-MM-dd') + } + catch { + $expirationDate = 'Unknown' + } + } + Write-Host " [Expired: $expirationDate] $filePath" -ForegroundColor Gray + } + } + + # Show other files (non-expired signature issues) + $otherFiles = $items | Where-Object { + -not ($_.Rules -and ($_.Rules | Where-Object { $_.Name -eq 'Binaries with expired signatures' })) + } + + if ($otherFiles.Count -gt 0) { + Write-Host "" + Write-Host " Other Files:" -ForegroundColor DarkCyan + + $displayLimit = [Math]::Min(20, $otherFiles.Count) + for ($i = 0; $i -lt $displayLimit; $i++) { + $item = $otherFiles[$i] + $identity = $item.Identity + $analysis = $item.Analysis + + $color = switch ($analysis) { + 'ERROR' { 'Red' } + 'WARNING' { 'Yellow' } + 'INFORMATION' { 'Green' } + 'DEBUG' { 'DarkGray' } + default { 'Gray' } + } + + if ($item.Rules -and $item.Rules.Count -gt 0) { + $ruleNames = $item.Rules | ForEach-Object { $_.Name } + Write-Host " [$analysis] $identity" -ForegroundColor $color + Write-Host " Rules: $($ruleNames -join ', ')" -ForegroundColor DarkGray + } + else { + Write-Host " [$analysis] $identity" -ForegroundColor $color + } + } + + if ($otherFiles.Count -gt $displayLimit) { + Write-Host " ... and $($otherFiles.Count - $displayLimit) more files" -ForegroundColor DarkGray + } + } + } + else { + # No expired signatures, show standard file listing + Write-Host "" + Write-Host " Files:" -ForegroundColor DarkCyan + + $displayLimit = [Math]::Min(50, $items.Count) + for ($i = 0; $i -lt $displayLimit; $i++) { + $item = $items[$i] + $identity = $item.Identity + $analysis = $item.Analysis + + $color = switch ($analysis) { + 'ERROR' { 'Red' } + 'WARNING' { 'Yellow' } + 'INFORMATION' { 'Green' } + 'DEBUG' { 'DarkGray' } + default { 'Gray' } + } + + # Show triggered rules for this file + if ($item.Rules -and $item.Rules.Count -gt 0) { + $ruleNames = $item.Rules | ForEach-Object { $_.Name } + Write-Host " [$analysis] $identity" -ForegroundColor $color + Write-Host " Rules: $($ruleNames -join ', ')" -ForegroundColor DarkGray + } + else { + Write-Host " [$analysis] $identity" -ForegroundColor $color + } + } + + if ($items.Count -gt $displayLimit) { + Write-Host " ... and $($items.Count - $displayLimit) more files" -ForegroundColor DarkGray + } + } + } + # Show details for non-file categories (users, groups, etc.) + elseif ($items.Count -gt 0) { + Write-Host "" + Write-Host " Items:" -ForegroundColor DarkCyan + + $displayLimit = [Math]::Min(20, $items.Count) + for ($i = 0; $i -lt $displayLimit; $i++) { + $item = $items[$i] + $identity = $item.Identity + $analysis = $item.Analysis + + $color = switch ($analysis) { + 'ERROR' { 'Red' } + 'WARNING' { 'Yellow' } + 'INFORMATION' { 'Green' } + 'DEBUG' { 'DarkGray' } + default { 'Gray' } + } + + Write-Host " [$analysis] $identity" -ForegroundColor $color + } + + if ($items.Count -gt $displayLimit) { + Write-Host " ... and $($items.Count - $displayLimit) more items" -ForegroundColor DarkGray + } + } + } + } + + Write-Host "" + Write-Host ("=" * 80) -ForegroundColor Cyan +} + +# Main execution +try { + # Validate input file + if (-not (Test-Path $Path)) { + Write-Error "ASA results file not found: $Path" + exit 1 + } + + Write-Verbose "Reading ASA results from: $Path" + + # Load and parse JSON + $jsonContent = Get-Content -Path $Path -Raw -Encoding UTF8 + $asaData = $jsonContent | ConvertFrom-Json -AsHashtable + + # Generate summary + Write-Verbose "Analyzing ASA results..." + $summary = Get-AsaSummary -AsaData $asaData -IncludeInformationalEvent:$IncludeInformationalEvent -IncludeDebugEvent:$IncludeDebugEvent + + # Output results to console + Write-ConsoleSummary -Summary $summary -ShowDetails:$ShowDetails -IncludeInformationalEvent:$IncludeInformationalEvent -IncludeDebugEvent:$IncludeDebugEvent +} +catch { + Write-Error "Error processing ASA results: $($_.Exception.Message)" + Write-Error $_.ScriptStackTrace + exit 1 +} diff --git a/tools/AttackSurfaceAnalyzer/docker/Dockerfile b/tools/AttackSurfaceAnalyzer/docker/Dockerfile new file mode 100644 index 00000000000..3e4aaa3b717 --- /dev/null +++ b/tools/AttackSurfaceAnalyzer/docker/Dockerfile @@ -0,0 +1,134 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# Multi-stage Dockerfile for Attack Surface Analyzer Testing +# Stage 1: Build and run ASA tests +# Stage 2: Extract reports to scratch layer + +# Stage 1: Test execution environment +FROM mcr.microsoft.com/dotnet/sdk:9.0-windowsservercore-ltsc2022@sha256:28f3a59216a7f91dfc4730ea47e236e2ffbb519975725bf8231f57e69dab3ca8 AS asa-runner + +# Set shell to PowerShell for easier scripting +SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"] + +# Install Attack Surface Analyzer as a global .NET tool +RUN dotnet tool install -g Microsoft.CST.AttackSurfaceAnalyzer.CLI --version 2.3.328 + +# Add .NET tools directory to PATH +RUN $env:PATH += ';C:/Users/ContainerAdministrator/.dotnet/tools'; \ + [Environment]::SetEnvironmentVariable('PATH', $env:PATH, [EnvironmentVariableTarget]::Machine) + +# Set working directory and create reports directory +WORKDIR C:/work +RUN New-Item -ItemType Directory -Path C:\reports -Force | Out-Null + +# Take baseline snapshot before installation +RUN Write-Host "=========================================" -ForegroundColor Green; \ + Write-Host "Taking baseline snapshot..." -ForegroundColor Green; \ + Write-Host "========================================="; \ + asa collect -f -r -u -l --directories 'C:\Program Files\PowerShell,C:\Program Files (x86)\PowerShell' --runid before; \ + if ($LASTEXITCODE -ne 0) { Write-Error "Failed to take baseline snapshot"; exit 1 } + +# Copy the PowerShell MSI file from build context +COPY *.msi ./powershell.msi + +# Install PowerShell MSI +RUN Write-Host "=========================================" -ForegroundColor Green; \ + Write-Host "Installing PowerShell MSI..." -ForegroundColor Green; \ + Write-Host "========================================="; \ + Write-Host "MSI file: C:\work\powershell.msi"; \ + $argumentList = '/i C:\work\powershell.msi /quiet /norestart /l*vx C:\work\install.log ADD_PATH=1'; \ + Write-Host "Running: msiexec $argumentList"; \ + $msiProcess = Start-Process msiexec.exe -ArgumentList $argumentList -Wait -NoNewWindow -PassThru; \ + if ($msiProcess.ExitCode -ne 0) { \ + Write-Host "MSI installation failed with exit code: $($msiProcess.ExitCode)"; \ + throw "MSI installation failed. Check install.log for details" \ + } + +# Take post-installation snapshot +RUN Write-Host "=========================================" -ForegroundColor Green; \ + Write-Host "Taking post-installation snapshot..." -ForegroundColor Green; \ + Write-Host "========================================="; \ + asa collect -f -r -u -l --directories 'C:\Program Files\PowerShell,C:\Program Files (x86)\PowerShell' --runid after; \ + if ($LASTEXITCODE -ne 0) { Write-Error "Failed to take post-installation snapshot"; exit 1 } + +# Export comparison results +RUN Write-Host "=========================================" -ForegroundColor Green; \ + Write-Host "Exporting comparison results..." -ForegroundColor Green; \ + Write-Host "========================================="; \ + asa export-collect --savetodatabase --resultlevels WARNING,ERROR,FATAL --firstrunid before --secondrunid after; \ + if ($LASTEXITCODE -ne 0) { Write-Warning "Failed to export results with exit code: $LASTEXITCODE" } + +# Export comparison results +RUN Write-Host "=========================================" -ForegroundColor Green; \ + Write-Host "Exporting comparison results..." -ForegroundColor Green; \ + Write-Host "========================================="; \ + asa export-collect --readfromsavedcomparisons; \ + if ($LASTEXITCODE -ne 0) { Write-Warning "Failed to export results with exit code: $LASTEXITCODE" } + + +# Copy and standardize JSON result files +RUN Write-Host "=========================================" -ForegroundColor Green; \ + Write-Host "Processing JSON result files..." -ForegroundColor Green; \ + Write-Host "========================================="; \ + $jsonFiles = Get-ChildItem -Path "*.json.txt" -ErrorAction SilentlyContinue; \ + if ($jsonFiles.Count -eq 0) { \ + Write-Warning 'No JSON.TXT files found - checking for .json files...'; \ + $jsonFiles = Get-ChildItem -Path "*.json" -ErrorAction SilentlyContinue \ + }; \ + if ($jsonFiles.Count -eq 0) { \ + throw 'No JSON files found - ASA may not have generated results' \ + } else { \ + $jsonFiles | ForEach-Object { \ + Write-Host "Found JSON file: $($_.Name)"; \ + if (-not (Test-Path $_.FullName)) { \ + throw "JSON file not accessible: $($_.FullName)" \ + }; \ + Write-Host "Copying to standard name: asa-results.json"; \ + Copy-Item -Path $_.FullName -Destination C:\work\asa-results.json -ErrorAction Stop; \ + Copy-Item -Path $_.FullName -Destination C:\reports\asa-results.json -ErrorAction Stop; \ + } \ + } + +# Copy SQLite database file if it exists +RUN Write-Host "=========================================" -ForegroundColor Green; \ + Write-Host "Copying SQLite database..." -ForegroundColor Green; \ + Write-Host "========================================="; \ + if (Test-Path "asa.sqlite") { \ + Write-Host "Copying: asa.sqlite"; \ + Copy-Item -Path "asa.sqlite" -Destination C:\reports\ -ErrorAction Stop \ + } else { \ + throw 'SQLite database (asa.sqlite) not found - this may indicate ASA export issues' \ + } + +# Copy installation log file +RUN Write-Host "=========================================" -ForegroundColor Green; \ + Write-Host "Copying installation log..." -ForegroundColor Green; \ + Write-Host "========================================="; \ + if (-not (Test-Path "C:\work\install.log")) { \ + throw "Required installation log file not found: C:\work\install.log" \ + }; \ + Copy-Item -Path "C:\work\install.log" -Destination C:\reports\ -ErrorAction Stop; \ + Write-Host "Attack Surface Analyzer test completed!" -ForegroundColor Green + +# Default command shows completion message +CMD Write-Host "=========================================" -ForegroundColor Green; \ + Write-Host "Container ready. Reports available in C:\reports\" -ForegroundColor Cyan; \ + Write-Host "=========================================" + +# Stage 2: Reports-only layer using minimal Windows base +FROM mcr.microsoft.com/windows/nanoserver:ltsc2022@sha256:307874138e4dc064d0538b58c6f028419ab82fb15fcabaf6d5378ba32c235266 AS asa-reports + +# Set working directory to root +WORKDIR / + +# Copy only the report files from the runner stage to root level +COPY --from=asa-runner C:/reports/ ./ + +# Stage 3: Final stage (defaults to the runner for backward compatibility) +FROM asa-runner AS final + +# Label for documentation +LABEL description="Windows container for running Attack Surface Analyzer tests on PowerShell MSI installations" +LABEL version="1.0" +LABEL maintainer="PowerShell Team" diff --git a/tools/UpdateDotnetRuntime.ps1 b/tools/UpdateDotnetRuntime.ps1 index 339153bcf0c..f62a16cfe14 100644 --- a/tools/UpdateDotnetRuntime.ps1 +++ b/tools/UpdateDotnetRuntime.ps1 @@ -9,9 +9,6 @@ param ( [Parameter()] [switch]$UseNuGetOrg, - [Parameter()] - [switch]$UpdateMSIPackaging, - [Parameter()] [string]$RuntimeSourceFeed, @@ -366,31 +363,6 @@ if ($dotnetUpdate.ShouldUpdate) { Write-Verbose -Message "Updating project files completed." -Verbose - if ($UpdateMSIPackaging) { - if (-not $environment.IsWindows) { - throw "UpdateMSIPackaging can only be done on Windows" - } - - Import-Module "$PSScriptRoot/../build.psm1" -Force - Import-Module "$PSScriptRoot/packaging" -Force - Start-PSBootstrap -Package - Start-PSBuild -Clean -Configuration Release -InteractiveAuth:$InteractiveAuth - - $publishPath = Split-Path (Get-PSOutput) - Remove-Item -Path "$publishPath\*.pdb" - - try { - Start-PSPackage -Type msi -SkipReleaseChecks -InformationVariable wxsData - } catch { - if ($_.Exception.Message -like "Current files to not match *") { - Copy-Item -Path $($wxsData.MessageData.NewFile) -Destination ($wxsData.MessageData.FilesWxsPath) - Write-Verbose -Message "Updating files.wxs file completed." -Verbose - } else { - throw $_ - } - } - } - Update-DevContainer } else { diff --git a/tools/WindowsCI.psm1 b/tools/WindowsCI.psm1 index 57d506bda8b..685882546c2 100644 --- a/tools/WindowsCI.psm1 +++ b/tools/WindowsCI.psm1 @@ -15,6 +15,8 @@ function New-LocalUser .OUTPUTS .NOTES #> + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingUsernameAndPasswordParams', '')] + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', '')] param( [Parameter(Mandatory=$true)] [string] $username, diff --git a/tools/buildCommon/startNativeExecution.ps1 b/tools/buildCommon/startNativeExecution.ps1 index ee7b00d04cd..2e44d86bd6a 100644 --- a/tools/buildCommon/startNativeExecution.ps1 +++ b/tools/buildCommon/startNativeExecution.ps1 @@ -16,6 +16,8 @@ function script:Start-NativeExecution { $ErrorActionPreference = "Continue" Write-Verbose "Executing: $ScriptBlock" try { + $cwd = Get-Location + if ($VerboseOutputOnError.IsPresent) { $output = & $ScriptBlock 2>&1 } else { @@ -36,10 +38,10 @@ function script:Start-NativeExecution { $callerFile = $callerLocationParts[0] $callerLine = $callerLocationParts[1] - $errorMessage = "Execution of {$ScriptBlock} by ${callerFile}: line $callerLine failed with exit code $LASTEXITCODE" + $errorMessage = "Execution of {$ScriptBlock} in '$cwd' by ${callerFile}: line $callerLine failed with exit code $LASTEXITCODE" throw $errorMessage } - throw "Execution of {$ScriptBlock} failed with exit code $LASTEXITCODE" + throw "Execution of {$ScriptBlock} in '$cwd' failed with exit code $LASTEXITCODE" } } finally { $ErrorActionPreference = $backupEAP diff --git a/tools/cgmanifest.json b/tools/cgmanifest/main/cgmanifest.json similarity index 73% rename from tools/cgmanifest.json rename to tools/cgmanifest/main/cgmanifest.json index f823d713d55..5d2de074cae 100644 --- a/tools/cgmanifest.json +++ b/tools/cgmanifest/main/cgmanifest.json @@ -35,7 +35,7 @@ "Type": "nuget", "Nuget": { "Name": "Json.More.Net", - "Version": "2.0.1.2" + "Version": "2.1.1" } }, "DevelopmentDependency": false @@ -45,7 +45,7 @@ "Type": "nuget", "Nuget": { "Name": "JsonPointer.Net", - "Version": "5.0.0" + "Version": "5.3.1" } }, "DevelopmentDependency": false @@ -55,7 +55,7 @@ "Type": "nuget", "Nuget": { "Name": "JsonSchema.Net", - "Version": "7.2.3" + "Version": "7.4.0" } }, "DevelopmentDependency": false @@ -65,7 +65,7 @@ "Type": "nuget", "Nuget": { "Name": "Markdig.Signed", - "Version": "0.38.0" + "Version": "1.1.2" } }, "DevelopmentDependency": false @@ -75,7 +75,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.ApplicationInsights", - "Version": "2.22.0" + "Version": "2.23.0" } }, "DevelopmentDependency": false @@ -85,7 +85,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Bcl.AsyncInterfaces", - "Version": "8.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -95,7 +95,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.CodeAnalysis.Analyzers", - "Version": "3.3.4" + "Version": "3.11.0" } }, "DevelopmentDependency": true @@ -105,7 +105,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.CodeAnalysis.Common", - "Version": "4.11.0" + "Version": "5.0.0" } }, "DevelopmentDependency": false @@ -115,7 +115,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.CodeAnalysis.CSharp", - "Version": "4.11.0" + "Version": "5.0.0" } }, "DevelopmentDependency": false @@ -125,17 +125,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Extensions.ObjectPool", - "Version": "8.0.11" - } - }, - "DevelopmentDependency": false - }, - { - "Component": { - "Type": "nuget", - "Nuget": { - "Name": "Microsoft.Management.Infrastructure.Runtime.Unix", - "Version": "3.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -150,16 +140,6 @@ }, "DevelopmentDependency": true }, - { - "Component": { - "Type": "nuget", - "Nuget": { - "Name": "Microsoft.Management.Infrastructure", - "Version": "3.0.0" - } - }, - "DevelopmentDependency": false - }, { "Component": { "Type": "nuget", @@ -170,22 +150,12 @@ }, "DevelopmentDependency": false }, - { - "Component": { - "Type": "nuget", - "Nuget": { - "Name": "Microsoft.PowerShell.Native", - "Version": "7.4.0" - } - }, - "DevelopmentDependency": false - }, { "Component": { "Type": "nuget", "Nuget": { "Name": "Microsoft.Security.Extensions", - "Version": "1.3.0" + "Version": "1.4.0" } }, "DevelopmentDependency": false @@ -195,17 +165,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Win32.Registry.AccessControl", - "Version": "9.0.0" - } - }, - "DevelopmentDependency": false - }, - { - "Component": { - "Type": "nuget", - "Nuget": { - "Name": "Microsoft.Win32.Registry", - "Version": "5.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -215,7 +175,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Win32.SystemEvents", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -225,7 +185,7 @@ "Type": "nuget", "Nuget": { "Name": "Microsoft.Windows.Compatibility", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -235,7 +195,7 @@ "Type": "nuget", "Nuget": { "Name": "Newtonsoft.Json", - "Version": "13.0.3" + "Version": "13.0.4" } }, "DevelopmentDependency": false @@ -245,7 +205,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-arm.runtime.native.System.IO.Ports", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -255,7 +215,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-arm64.runtime.native.System.IO.Ports", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -265,7 +225,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-x64.runtime.native.System.IO.Ports", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -275,7 +235,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.android-x86.runtime.native.System.IO.Ports", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -285,7 +245,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-arm.runtime.native.System.IO.Ports", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -295,7 +255,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-arm64.runtime.native.System.IO.Ports", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -305,7 +265,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-bionic-arm64.runtime.native.System.IO.Ports", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -315,7 +275,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-bionic-x64.runtime.native.System.IO.Ports", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -325,7 +285,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-musl-arm.runtime.native.System.IO.Ports", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -335,7 +295,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-musl-arm64.runtime.native.System.IO.Ports", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -345,7 +305,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-musl-x64.runtime.native.System.IO.Ports", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -355,7 +315,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.linux-x64.runtime.native.System.IO.Ports", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -365,7 +325,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.maccatalyst-arm64.runtime.native.System.IO.Ports", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -375,7 +335,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.maccatalyst-x64.runtime.native.System.IO.Ports", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -385,7 +345,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.native.System.Data.SqlClient.sni", - "Version": "4.7.0" + "Version": "4.4.0" } }, "DevelopmentDependency": false @@ -395,7 +355,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.native.System.IO.Ports", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -405,7 +365,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.osx-arm64.runtime.native.System.IO.Ports", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -415,7 +375,7 @@ "Type": "nuget", "Nuget": { "Name": "runtime.osx-x64.runtime.native.System.IO.Ports", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -475,17 +435,7 @@ "Type": "nuget", "Nuget": { "Name": "System.CodeDom", - "Version": "9.0.0" - } - }, - "DevelopmentDependency": false - }, - { - "Component": { - "Type": "nuget", - "Nuget": { - "Name": "System.Collections.Immutable", - "Version": "8.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -495,7 +445,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ComponentModel.Composition.Registration", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -505,7 +455,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ComponentModel.Composition", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -515,7 +465,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Configuration.ConfigurationManager", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -525,7 +475,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Data.Odbc", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -535,7 +485,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Data.OleDb", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -545,17 +495,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Data.SqlClient", - "Version": "4.8.6" - } - }, - "DevelopmentDependency": false - }, - { - "Component": { - "Type": "nuget", - "Nuget": { - "Name": "System.Diagnostics.DiagnosticSource", - "Version": "9.0.0" + "Version": "4.9.1" } }, "DevelopmentDependency": false @@ -565,7 +505,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Diagnostics.EventLog", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -575,7 +515,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Diagnostics.PerformanceCounter", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -585,7 +525,7 @@ "Type": "nuget", "Nuget": { "Name": "System.DirectoryServices.AccountManagement", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -595,7 +535,7 @@ "Type": "nuget", "Nuget": { "Name": "System.DirectoryServices.Protocols", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -605,7 +545,7 @@ "Type": "nuget", "Nuget": { "Name": "System.DirectoryServices", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -615,7 +555,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Drawing.Common", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -625,7 +565,7 @@ "Type": "nuget", "Nuget": { "Name": "System.IO.Packaging", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -635,7 +575,7 @@ "Type": "nuget", "Nuget": { "Name": "System.IO.Ports", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -645,7 +585,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Management", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -655,27 +595,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Net.Http.WinHttpHandler", - "Version": "9.0.0" - } - }, - "DevelopmentDependency": false - }, - { - "Component": { - "Type": "nuget", - "Nuget": { - "Name": "System.Numerics.Vectors", - "Version": "4.5.0" - } - }, - "DevelopmentDependency": false - }, - { - "Component": { - "Type": "nuget", - "Nuget": { - "Name": "System.Private.ServiceModel", - "Version": "4.10.3" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -685,27 +605,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Reflection.Context", - "Version": "9.0.0" - } - }, - "DevelopmentDependency": false - }, - { - "Component": { - "Type": "nuget", - "Nuget": { - "Name": "System.Reflection.DispatchProxy", - "Version": "4.7.1" - } - }, - "DevelopmentDependency": false - }, - { - "Component": { - "Type": "nuget", - "Nuget": { - "Name": "System.Reflection.Metadata", - "Version": "8.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -715,17 +615,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Runtime.Caching", - "Version": "9.0.0" - } - }, - "DevelopmentDependency": false - }, - { - "Component": { - "Type": "nuget", - "Nuget": { - "Name": "System.Security.AccessControl", - "Version": "6.0.1" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -735,7 +625,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Cryptography.Pkcs", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -745,7 +635,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Cryptography.ProtectedData", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -755,7 +645,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Cryptography.Xml", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -765,17 +655,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Security.Permissions", - "Version": "9.0.0" - } - }, - "DevelopmentDependency": false - }, - { - "Component": { - "Type": "nuget", - "Nuget": { - "Name": "System.Security.Principal.Windows", - "Version": "5.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -784,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 @@ -794,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 @@ -805,7 +685,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ServiceModel.NetTcp", - "Version": "4.10.3" + "Version": "10.0.652802" } }, "DevelopmentDependency": false @@ -815,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 @@ -835,7 +705,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ServiceModel.Syndication", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -845,7 +715,7 @@ "Type": "nuget", "Nuget": { "Name": "System.ServiceProcess.ServiceController", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -855,37 +725,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Speech", - "Version": "9.0.0" - } - }, - "DevelopmentDependency": false - }, - { - "Component": { - "Type": "nuget", - "Nuget": { - "Name": "System.Text.Encoding.CodePages", - "Version": "9.0.0" - } - }, - "DevelopmentDependency": false - }, - { - "Component": { - "Type": "nuget", - "Nuget": { - "Name": "System.Text.Encodings.Web", - "Version": "9.0.0" - } - }, - "DevelopmentDependency": false - }, - { - "Component": { - "Type": "nuget", - "Nuget": { - "Name": "System.Threading.AccessControl", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false @@ -895,7 +735,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Web.Services.Description", - "Version": "4.10.0" + "Version": "8.1.2" } }, "DevelopmentDependency": false @@ -905,7 +745,7 @@ "Type": "nuget", "Nuget": { "Name": "System.Windows.Extensions", - "Version": "9.0.0" + "Version": "10.0.6" } }, "DevelopmentDependency": false diff --git a/tools/cgmanifest/tpn/cgmanifest.json b/tools/cgmanifest/tpn/cgmanifest.json new file mode 100644 index 00000000000..a0746028a56 --- /dev/null +++ b/tools/cgmanifest/tpn/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": "0.45.0" + } + }, + "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.3" + } + }, + "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.3" + } + }, + "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.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "Microsoft.Win32.SystemEvents", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "Microsoft.Windows.Compatibility", + "Version": "10.0.3" + } + }, + "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.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.android-arm64.runtime.native.System.IO.Ports", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.android-x64.runtime.native.System.IO.Ports", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.android-x86.runtime.native.System.IO.Ports", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.linux-arm.runtime.native.System.IO.Ports", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.linux-arm64.runtime.native.System.IO.Ports", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.linux-bionic-arm64.runtime.native.System.IO.Ports", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.linux-bionic-x64.runtime.native.System.IO.Ports", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.linux-musl-arm.runtime.native.System.IO.Ports", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.linux-musl-arm64.runtime.native.System.IO.Ports", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.linux-musl-x64.runtime.native.System.IO.Ports", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.linux-x64.runtime.native.System.IO.Ports", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.maccatalyst-arm64.runtime.native.System.IO.Ports", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.maccatalyst-x64.runtime.native.System.IO.Ports", + "Version": "10.0.3" + } + }, + "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.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.osx-arm64.runtime.native.System.IO.Ports", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "runtime.osx-x64.runtime.native.System.IO.Ports", + "Version": "10.0.3" + } + }, + "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.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.ComponentModel.Composition.Registration", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.ComponentModel.Composition", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Configuration.ConfigurationManager", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Data.Odbc", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Data.OleDb", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Data.SqlClient", + "Version": "4.9.0" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Diagnostics.EventLog", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Diagnostics.PerformanceCounter", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.DirectoryServices.AccountManagement", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.DirectoryServices.Protocols", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.DirectoryServices", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Drawing.Common", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.IO.Packaging", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.IO.Ports", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Management", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Net.Http.WinHttpHandler", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Reflection.Context", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Runtime.Caching", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Security.Cryptography.Pkcs", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Security.Cryptography.ProtectedData", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Security.Cryptography.Xml", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Security.Permissions", + "Version": "10.0.3" + } + }, + "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.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.ServiceProcess.ServiceController", + "Version": "10.0.3" + } + }, + "DevelopmentDependency": false + }, + { + "Component": { + "Type": "nuget", + "Nuget": { + "Name": "System.Speech", + "Version": "10.0.3" + } + }, + "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.3" + } + }, + "DevelopmentDependency": false + } + ], + "$schema": "https://json.schemastore.org/component-detection-manifest.json" +} diff --git a/tools/ci.psm1 b/tools/ci.psm1 index 6628d54e043..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' @@ -17,8 +20,15 @@ if(Test-Path $dotNetPath) # import build into the global scope so it can be used by packaging # argumentList $true says ignore tha we may not be able to build -Import-Module (Join-Path $repoRoot 'build.psm1') -Verbose -Scope Global -ArgumentList $true -Import-Module (Join-Path $repoRoot 'tools\packaging') -Verbose -Scope Global +Write-Verbose "Importing build.psm1" -Verbose +Import-Module (Join-Path $repoRoot 'build.psm1') -Scope Global -ArgumentList $true +$buildCommands = Get-Command -Module build +Write-Verbose "Imported build.psm1 commands: $($buildCommands.Count)" -Verbose + +Write-Verbose "Importing packaging.psm1" -Verbose +Import-Module (Join-Path $repoRoot 'tools\packaging') -Scope Global +$packagingCommands = Get-Command -Module packaging +Write-Verbose "Imported packaging.psm1 commands: $($packagingCommands.Count)" -Verbose # import the windows specific functcion only in Windows PowerShell or on Windows if($PSVersionTable.PSEdition -eq 'Desktop' -or $IsWindows) @@ -91,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 @@ -105,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) @@ -181,8 +196,6 @@ function Invoke-CIInstall } Set-BuildVariable -Name TestPassed -Value False - Write-Verbose -Verbose -Message "Calling Start-PSBootstrap from Invoke-CIInstall" - Start-PSBootstrap } function Invoke-CIxUnit @@ -215,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 { @@ -224,9 +276,12 @@ function Invoke-CITest [string] $Purpose, [ValidateSet('CI', 'Others')] [string] $TagSet, - [string] $TitlePrefix + [string] $TitlePrefix, + [string] $OutputFormat = "NUnitXml" ) + Write-Verbose -Verbose "CI test: OutputFormat: $OutputFormat" + # Set locale correctly for Linux CIs Set-CorrectLocale @@ -249,7 +304,7 @@ function Invoke-CITest if($IsLinux -or $IsMacOS) { - return Invoke-LinuxTestsCore -Purpose $Purpose -ExcludeTag $ExcludeTag -TagSet $TagSet -TitlePrefix $TitlePrefix + return Invoke-LinuxTestsCore -Purpose $Purpose -ExcludeTag $ExcludeTag -TagSet $TagSet -TitlePrefix $TitlePrefix -OutputFormat $OutputFormat } # CoreCLR @@ -281,12 +336,14 @@ function Invoke-CITest Terse = $true Tag = @() ExcludeTag = $ExcludeTag + 'RequireAdminOnWindows' + OutputFormat = $OutputFormat } $title = "Pester Unelevated - $TagSet" if ($TitlePrefix) { $title = "$TitlePrefix - $title" } + Write-Verbose -Verbose "Starting Pester with output format $($arguments.OutputFormat)" Start-PSPester @arguments -Title $title # Fail the build, if tests failed @@ -314,7 +371,10 @@ function Invoke-CITest if ($TitlePrefix) { $title = "$TitlePrefix - $title" } - Start-PSPester @arguments -Title $title + + # We just built the test tools, we don't need to rebuild them + Write-Verbose -Verbose "Starting Pester with output format $($arguments.OutputFormat)" + Start-PSPester @arguments -Title $title -SkipTestToolBuild # Fail the build, if tests failed Test-PSPesterResults -TestResultsFile $expFeatureTestResultFile @@ -328,12 +388,15 @@ function Invoke-CITest OutputFile = $testResultsAdminFile Tag = @('RequireAdminOnWindows') ExcludeTag = $ExcludeTag + OutputFormat = $OutputFormat } $title = "Pester Elevated - $TagSet" if ($TitlePrefix) { $title = "$TitlePrefix - $title" } + + Write-Verbose -Verbose "Starting Pester with output format $($arguments.OutputFormat)" Start-PSPester @arguments -Title $title # Fail the build, if tests failed @@ -364,7 +427,10 @@ function Invoke-CITest if ($TitlePrefix) { $title = "$TitlePrefix - $title" } - Start-PSPester @arguments -Title $title + + Write-Verbose -Verbose "Starting Pester with output format $($arguments.OutputFormat)" + # We just built the test tools, we don't need to rebuild them + Start-PSPester @arguments -Title $title -SkipTestToolBuild # Fail the build, if tests failed Test-PSPesterResults -TestResultsFile $expFeatureTestResultFile @@ -381,8 +447,6 @@ function New-CodeCoverageAndTestPackage if (Test-DailyBuild) { - Start-PSBootstrap -Verbose - Start-PSBuild -Configuration 'CodeCoverage' -Clean $codeCoverageOutput = Split-Path -Parent (Get-PSOutput) @@ -437,6 +501,18 @@ function Push-Artifact if ($env:TF_BUILD) { # In Azure DevOps Write-Host "##vso[artifact.upload containerfolder=$artifactName;artifactname=$artifactName;]$Path" + } elseif ($env:GITHUB_WORKFLOW -and $env:RUNNER_WORKSPACE) { + # In GitHub Actions + $destinationPath = Join-Path -Path $env:RUNNER_WORKSPACE -ChildPath $artifactName + + # Create the folder if it does not exist + if (!(Test-Path -Path $destinationPath)) { + $null = New-Item -ItemType Directory -Path $destinationPath -Force + } + + Copy-Item -Path $Path -Destination $destinationPath -Force -Verbose + } else { + Write-Warning "Push-Artifact is not supported in this environment." } } @@ -540,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) { @@ -565,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 $_ @@ -651,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 { @@ -658,7 +700,7 @@ function Invoke-BootstrapStage Write-Log -Message "Executing ci.psm1 Bootstrap Stage" # Make sure we have all the tags Sync-PSTags -AddRemoteIfMissing - Start-PSBootstrap -Package:$createPackages + Start-PSBootstrap -Scenario Package:$createPackages } # Run pester tests for Linux and macOS @@ -670,7 +712,8 @@ function Invoke-LinuxTestsCore [string] $Purpose = 'All', [string[]] $ExcludeTag = @('Slow', 'Feature', 'Scenario'), [string] $TagSet = 'CI', - [string] $TitlePrefix + [string] $TitlePrefix, + [string] $OutputFormat = "NUnitXml" ) $output = Split-Path -Parent (Get-PSOutput -Options (Get-PSOptions)) @@ -683,12 +726,13 @@ function Invoke-LinuxTestsCore $sudoResultsWithExpFeatures = $null $noSudoPesterParam = @{ - 'BinDir' = $output - 'PassThru' = $true - 'Terse' = $true - 'Tag' = @() - 'ExcludeTag' = $testExcludeTag - 'OutputFile' = $testResultsNoSudo + 'BinDir' = $output + 'PassThru' = $true + 'Terse' = $true + 'Tag' = @() + 'ExcludeTag' = $testExcludeTag + 'OutputFile' = $testResultsNoSudo + 'OutputFormat' = $OutputFormat } # Get the experimental feature names and the tests associated with them @@ -726,7 +770,7 @@ function Invoke-LinuxTestsCore if ($TitlePrefix) { $title = "$TitlePrefix - $title" } - $passThruResult = Start-PSPester @noSudoPesterParam -Title $title + $passThruResult = Start-PSPester @noSudoPesterParam -Title $title -SkipTestToolBuild $noSudoResultsWithExpFeatures += $passThruResult } @@ -741,6 +785,7 @@ function Invoke-LinuxTestsCore $sudoPesterParam['ExcludeTag'] = $ExcludeTag $sudoPesterParam['Sudo'] = $true $sudoPesterParam['OutputFile'] = $testResultsSudo + $sudoPesterParam['OutputFormat'] = $OutputFormat $title = "Pester Sudo - $TagSet" if ($TitlePrefix) { @@ -773,7 +818,9 @@ function Invoke-LinuxTestsCore if ($TitlePrefix) { $title = "$TitlePrefix - $title" } - $passThruResult = Start-PSPester @sudoPesterParam -Title $title + + # We just built the test tools for the main test run, we don't need to rebuild them + $passThruResult = Start-PSPester @sudoPesterParam -Title $title -SkipTestToolBuild $sudoResultsWithExpFeatures += $passThruResult } @@ -837,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 } } @@ -907,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 490edebb81b..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 = 'net9.0' - $dotnetTargetNameWin7 = 'net9.0-windows8.0' + $baseTargetName = $script:dotnetTargetName $unixProjectName = 'powershell-unix' $windowsProjectName = 'powershell-win-core' $actualRuntime = $Runtime @@ -202,29 +242,30 @@ function Get-CGRegistrations { switch -regex ($Runtime) { "alpine-.*" { $folder = $unixProjectName - $target = "$dotnetTargetName|$Runtime" + $target = "$baseTargetName|$Runtime" + $neutralTarget = "$baseTargetName" } "linux-.*" { $folder = $unixProjectName - $target = "$dotnetTargetName|$Runtime" + $target = "$baseTargetName|$Runtime" + $neutralTarget = "$baseTargetName" } "osx-.*" { $folder = $unixProjectName - $target = "$dotnetTargetName|$Runtime" - } - "win-x*" { - $sdkToUse = $winDesktopSdk - $folder = $windowsProjectName - $target = "$dotnetTargetNameWin7|$Runtime" + $target = "$baseTargetName|$Runtime" + $neutralTarget = "$baseTargetName" } "win-.*" { + $sdkToUse = $winDesktopSdk $folder = $windowsProjectName - $target = "$dotnetTargetNameWin7|$Runtime" + $target = "$baseTargetName|$actualRuntime" + $neutralTarget = "$baseTargetName" } "modules" { $folder = "modules" $actualRuntime = 'linux-x64' - $target = "$dotnetTargetName|$actualRuntime" + $target = "$baseTargetName|$actualRuntime" + $neutralTarget = "$baseTargetName" } Default { throw "Invalid runtime name: $Runtime" @@ -239,8 +280,53 @@ 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 } catch { Get-ChildItem -Path "${folder}:/targets" | Out-String | Write-Verbose -Verbose throw @@ -250,27 +336,53 @@ function Get-CGRegistrations { Get-PSDrive -Name $folder -ErrorAction Ignore | Remove-PSDrive } + # Name to skip for TPN generation + $skipNames = @( + "Microsoft.PowerShell.Native" + "Microsoft.Management.Infrastructure.Runtime.Unix" + "Microsoft.Management.Infrastructure" + "Microsoft.PowerShell.Commands.Diagnostics" + "Microsoft.PowerShell.Commands.Management" + "Microsoft.PowerShell.Commands.Utility" + "Microsoft.PowerShell.ConsoleHost" + "Microsoft.PowerShell.SDK" + "Microsoft.PowerShell.Security" + "Microsoft.Management.Infrastructure.CimCmdlets" + "Microsoft.WSMan.Management" + "Microsoft.WSMan.Runtime" + "System.Management.Automation" + "Microsoft.PowerShell.GraphicalHost" + "Microsoft.PowerShell.CoreCLR.Eventing" + ) + + Write-Verbose "Found $($targets.Count) targets to process..." -Verbose $targets | ForEach-Object { $target = $_ $parts = ($target -split '\|') $name = $parts[0] - $targetVersion = $parts[1] - $publicVersion = Get-NuGetPublicVersion -Name $name -Version $targetVersion - - # Add the registration to the cgmanifest if the TPN does not contain the name of the target OR - # the exisitng CG contains the registration, because if the existing CG contains the registration, - # that might be the only reason it is in the TPN. - if (!$RegistrationTable.ContainsKey($target)) { - $DevelopmentDependency = $false - if (!$existingRegistrationTable.ContainsKey($name) -or $existingRegistrationTable.$name.Component.Version() -ne $publicVersion) { - $registrationChanged = $true - } - if ($existingRegistrationTable.ContainsKey($name) -and $existingRegistrationTable.$name.DevelopmentDependency) { - $DevelopmentDependency = $true - } - $registration = New-NugetComponent -Name $name -Version $publicVersion -DevelopmentDependency:$DevelopmentDependency - $RegistrationTable.Add($target, $registration) + if ($name -in $skipNames) { + Write-Verbose "Skipping $name..." + + } else { + $targetVersion = $parts[1] + $publicVersion = Get-NuGetPublicVersion -Name $name -Version $targetVersion + + # Add the registration to the cgmanifest if the TPN does not contain the name of the target OR + # the exisitng CG contains the registration, because if the existing CG contains the registration, + # that might be the only reason it is in the TPN. + if (!$RegistrationTable.ContainsKey($target)) { + $DevelopmentDependency = $false + if (!$existingRegistrationTable.ContainsKey($name) -or $existingRegistrationTable.$name.Component.Version() -ne $publicVersion) { + $registrationChanged = $true + } + if ($existingRegistrationTable.ContainsKey($name) -and $existingRegistrationTable.$name.DevelopmentDependency) { + $DevelopmentDependency = $true + } + + $registration = New-NugetComponent -Name $name -Version $publicVersion -DevelopmentDependency:$DevelopmentDependency + $RegistrationTable.Add($target, $registration) + } } } @@ -302,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 @@ -312,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 627d7332d17..b2c8b561502 100644 --- a/tools/metadata.json +++ b/tools/metadata.json @@ -1,10 +1,10 @@ { - "StableReleaseTag": "v7.4.6", - "PreviewReleaseTag": "v7.5.0-rc.1", + "StableReleaseTag": "v7.6.1", + "PreviewReleaseTag": "v7.7.0-preview.1", "ServicingReleaseTag": "v7.0.13", - "ReleaseTag": "v7.4.6", - "LTSReleaseTag" : ["v7.2.24", "v7.4.6"], - "NextReleaseTag": "v7.5.0-preview.6", - "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 c9fd280930f..8d8154e7445 100644 --- a/tools/packaging/boms/windows.json +++ b/tools/packaging/boms/windows.json @@ -1,3478 +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/*.dll", - "FileType": "NonProduct" - }, - { - "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", + "Architecture": null + }, + { + "Pattern": "Modules\\Microsoft.PowerShell.ThreadJob\\en-us\\Microsoft.PowerShell.ThreadJob.dll-Help.xml", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "Modules\\Microsoft.PowerShell.ThreadJob\\LICENSE", + "FileType": "NonProduct", + "Architecture": null + }, + { + "Pattern": "Modules\\Microsoft.PowerShell.ThreadJob\\ThirdPartyNotices.txt", + "FileType": "NonProduct", + "Architecture": null }, { "Pattern": "Modules\\PSReadLine\\.signature.p7s", - "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", + "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", + "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", + "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", + "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", + "Architecture": null + }, + { + "Pattern": "Modules\\Microsoft.PowerShell.ThreadJob\\Microsoft.PowerShell.ThreadJob.psd1", + "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": "RegisterMicrosoftUpdate.ps1", - "FileType": "Product" + "Pattern": "pwsh.profile.resource.ps1", + "FileType": "Product", + "Architecture": null + }, + { + "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 75f09daa160..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 = 'net9.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 + } } +} - return $Arguments +function New-MacOSPackage +{ + [CmdletBinding(SupportsShouldProcess=$true)] + param( + [Parameter(Mandatory)] + [string]$Name, + + [Parameter(Mandatory)] + [string]$Version, + + [Parameter(Mandatory)] + [string]$Iteration, + + [Parameter(Mandatory)] + [string]$Staging, + + [Parameter(Mandatory)] + [string]$Destination, + + [Parameter(Mandatory)] + [string]$ManGzipFile, + + [Parameter(Mandatory)] + [string]$ManDestination, + + [Parameter(Mandatory)] + [LinkInfo[]]$LinkInfo, + + [Parameter(Mandatory)] + [string]$AfterInstallScript, + + [Parameter(Mandatory)] + [string]$AppsFolder, + + [Parameter(Mandatory)] + [string]$HostArchitecture, + + [string]$CurrentLocation = (Get-Location), + + [switch]$LTS + ) + + Write-Log "Creating macOS package using pkgbuild and productbuild..." + + # Create a temporary directory for package building + $tempRoot = New-TempFolder + $componentPkgPath = Join-Path $tempRoot "component.pkg" + $scriptsDir = Join-Path $tempRoot "scripts" + $resourcesDir = Join-Path $tempRoot "resources" + $distributionFile = Join-Path $tempRoot "distribution.xml" + + try { + # Create scripts directory + New-Item -ItemType Directory -Path $scriptsDir -Force | Out-Null + + # Copy and prepare the postinstall script + $postInstallPath = Join-Path $scriptsDir "postinstall" + Copy-Item -Path $AfterInstallScript -Destination $postInstallPath -Force + Start-NativeExecution { + chmod 755 $postInstallPath + } + + # Create a temporary directory for the package root + $pkgRoot = Join-Path $tempRoot "pkgroot" + New-Item -ItemType Directory -Path $pkgRoot -Force | Out-Null + + # Copy staging files to destination path in package root + $destInPkg = Join-Path $pkgRoot $Destination + New-Item -ItemType Directory -Path $destInPkg -Force | Out-Null + Write-Verbose "Copying staging files from $Staging to $destInPkg" -Verbose + Copy-Item -Path "$Staging/*" -Destination $destInPkg -Recurse -Force + + # Create man page directory structure + $manDir = Join-Path $pkgRoot (Split-Path $ManDestination -Parent) + New-Item -ItemType Directory -Path $manDir -Force | Out-Null + Copy-Item -Path $ManGzipFile -Destination (Join-Path $pkgRoot $ManDestination) -Force + + # Create symlinks in package root + # The LinkInfo contains Source (a temp file that IS a symlink) and Destination (where to install it) + foreach ($link in $LinkInfo) { + $linkDestDir = Join-Path $pkgRoot (Split-Path $link.Destination -Parent) + New-Item -ItemType Directory -Path $linkDestDir -Force | Out-Null + $finalLinkPath = Join-Path $pkgRoot $link.Destination + + Write-Verbose "Creating symlink at $finalLinkPath" -Verbose + + # Remove if exists + if (Test-Path $finalLinkPath) { + Remove-Item $finalLinkPath -Force + } + + # Get the target of the original symlink and recreate it in the package root + if (Test-Path $link.Source) { + $linkTarget = (Get-Item $link.Source).Target + if ($linkTarget) { + Write-Verbose "Creating symlink to target: $linkTarget" -Verbose + New-Item -ItemType SymbolicLink -Path $finalLinkPath -Target $linkTarget -Force | Out-Null + } else { + Write-Warning "Could not determine target for symlink at $($link.Source), copying file instead" + Copy-Item -Path $link.Source -Destination $finalLinkPath -Force + } + } else { + Write-Warning "Source symlink $($link.Source) does not exist" + } + } + + # Copy launcher app folder if provided + if ($AppsFolder) { + $appsInPkg = Join-Path $pkgRoot "Applications" + New-Item -ItemType Directory -Path $appsInPkg -Force | Out-Null + Write-Verbose "Copying launcher app from $AppsFolder to $appsInPkg" -Verbose + Copy-Item -Path "$AppsFolder/*" -Destination $appsInPkg -Recurse -Force + } + + # Get package identifier info based on version and LTS flag + $packageInfo = Get-MacOSPackageIdentifierInfo -Version $Version -LTS:$LTS + $IsPreview = $packageInfo.IsPreview + $pkgIdentifier = $packageInfo.PackageIdentifier + + if ($PSCmdlet.ShouldProcess("Build component package with pkgbuild")) { + Write-Log "Running pkgbuild to create component package..." + + Start-NativeExecution -VerboseOutputOnError { + pkgbuild --root $pkgRoot ` + --identifier $pkgIdentifier ` + --version $Version ` + --scripts $scriptsDir ` + --install-location "/" ` + $componentPkgPath + } + + Write-Verbose "Component package created: $componentPkgPath" -Verbose + } + + # Create the final distribution package using the refactored function + $distributionPackage = New-MacOsDistributionPackage ` + -ComponentPackage (Get-Item $componentPkgPath) ` + -PackageName $Name ` + -Version $Version ` + -OutputDirectory $CurrentLocation ` + -HostArchitecture $HostArchitecture ` + -PackageIdentifier $pkgIdentifier ` + -IsPreview:$IsPreview + + return $distributionPackage + } + finally { + # Clean up temporary directory + if (Test-Path $tempRoot) { + Write-Verbose "Cleaning up temporary directory: $tempRoot" -Verbose + Remove-Item -Path $tempRoot -Recurse -Force -ErrorAction SilentlyContinue + } + } } function Get-PackageDependencies @@ -1570,7 +2133,7 @@ function Get-PackageDependencies param() DynamicParam { # Add a dynamic parameter '-Distribution' when the specified package type is 'deb'. - # The '-Distribution' parameter can be used to indicate which Debian distro this pacakge is targeting. + # The '-Distribution' parameter can be used to indicate which Debian distro this package is targeting. $ParameterAttr = New-Object "System.Management.Automation.ParameterAttribute" $ParameterAttr.Mandatory = $true $ValidateSetAttr = New-Object "System.Management.Automation.ValidateSetAttribute" -ArgumentList $Script:AllDistributions @@ -1591,6 +2154,22 @@ function Get-PackageDependencies # These should match those in the Dockerfiles, but exclude tools like Git, which, and curl $Dependencies = @() + + # ICU version range follows .NET runtime policy. + # See: https://github.com/dotnet/runtime/blob/3fe8518d51bbcaa179bbe275b2597fbe1b88bc5a/src/native/libs/System.Globalization.Native/pal_icushim.c#L235-L243 + # + # Version range rationale: + # - The runtime supports ICU versions >= the version it was built against + # and <= that version + 30, to allow sufficient headroom for future releases. + # - ICU typically releases about twice per year, so +30 provides roughly + # 15 years of forward compatibility. + # - On some platforms, the minimum supported version may be lower + # than the build version and we know that older versions just works. + # + $MinICUVersion = 60 # runtime minimum supported + $BuildICUVersion = 76 # current build version + $MaxICUVersion = $BuildICUVersion + 30 # headroom + if ($Distribution -eq 'deb') { $Dependencies = @( "libc6", @@ -1598,10 +2177,9 @@ function Get-PackageDependencies "libgssapi-krb5-2", "libstdc++6", "zlib1g", - "libicu74|libicu72|libicu71|libicu70|libicu69|libicu68|libicu67|libicu66|libicu65|libicu63|libicu60|libicu57|libicu55|libicu52", + (($MaxICUVersion..$MinICUVersion).ForEach{ "libicu$_" } -join '|'), "libssl3|libssl1.1|libssl1.0.2|libssl1.0.0" ) - } elseif ($Distribution -eq 'rh') { $Dependencies = @( "openssl-libs", @@ -1621,7 +2199,7 @@ function Get-PackageDependencies ) if($Script:Options.Runtime -like 'fx*') { $Dependencies += @( - "dotnet-runtime-9.0" + "dotnet-runtime-10.0" ) } } elseif ($Distribution -eq 'macOS') { @@ -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 -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 -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 fda97df9cdc..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="net9.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 cda0bb83859..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>net9.0</TargetFramework> + <TargetFramework>net11.0</TargetFramework> <Version>$(RefAsmVersion)</Version> <DelaySign>true</DelaySign> <AssemblyOriginatorKeyFile>$(SnkFile)</AssemblyOriginatorKeyFile> <SignAssembly>true</SignAssembly> - <LangVersion>11.0</LangVersion> + <LangVersion>preview</LangVersion> </PropertyGroup> <ItemGroup> <Reference Include="System.Management.Automation"> @@ -14,7 +14,8 @@ </ItemGroup> <ItemGroup> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> - <PackageReference Include="System.Security.AccessControl" Version="6.0.0" /> + <!-- Removing System.Security.AccessControl as per NU1510 --> + <!-- PackageReference Include="System.Security.AccessControl" Version="6.0.0" /--> <PackageReference Include="Microsoft.PowerShell.MarkdownRender" Version="7.2.1" /> </ItemGroup> </Project> 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 37ea87be80f..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>net9.0</TargetFramework> + <TargetFramework>net11.0</TargetFramework> <Version>$(RefAsmVersion)</Version> <DelaySign>true</DelaySign> <AssemblyOriginatorKeyFile>$(SnkFile)</AssemblyOriginatorKeyFile> <SignAssembly>true</SignAssembly> - <LangVersion>11.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 d4e2b460dcb..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,14 +1,15 @@ <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> - <TargetFramework>net9.0</TargetFramework> + <TargetFramework>net11.0</TargetFramework> <Version>$(RefAsmVersion)</Version> <DelaySign>true</DelaySign> <AssemblyOriginatorKeyFile>$(SnkFile)</AssemblyOriginatorKeyFile> <SignAssembly>true</SignAssembly> - <LangVersion>11.0</LangVersion> + <LangVersion>preview</LangVersion> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.Management.Infrastructure" Version="3.0.0" /> - <PackageReference Include="System.Security.AccessControl" Version="6.0.0" /> + <!-- PackageReference Include="System.Security.AccessControl" Version="6.0.0" /--> + <!-- PackageReference Include="System.Security.AccessControl" Version="6.0.0" /--> </ItemGroup> </Project> diff --git a/tools/releaseBuild/.gitignore b/tools/releaseBuild/.gitignore deleted file mode 100644 index 0ff566888a7..00000000000 --- a/tools/releaseBuild/.gitignore +++ /dev/null @@ -1 +0,0 @@ -PSRelease/ diff --git a/tools/releaseBuild/Images/GenericLinuxFiles/PowerShellPackage.ps1 b/tools/releaseBuild/Images/GenericLinuxFiles/PowerShellPackage.ps1 deleted file mode 100644 index 2475dce7d89..00000000000 --- a/tools/releaseBuild/Images/GenericLinuxFiles/PowerShellPackage.ps1 +++ /dev/null @@ -1,145 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -# PowerShell Script to build and package PowerShell from specified form and branch -# Script is intented to use in Docker containers -# Ensure PowerShell is available in the provided image - -param ( - [string] $location = "/powershell", - - # Destination location of the package on docker host - [string] $destination = '/mnt', - - [ValidatePattern("^v\d+\.\d+\.\d+(-\w+(\.\d{1,2})?)?$")] - [ValidateNotNullOrEmpty()] - [string]$ReleaseTag, - - [switch]$TarX64, - [switch]$TarArm, - [switch]$TarArm64, - [switch]$TarMinSize, - [switch]$FxDependent, - [switch]$Alpine -) - -$releaseTagParam = @{} -if ($ReleaseTag) -{ - $releaseTagParam = @{ 'ReleaseTag' = $ReleaseTag } -} - -#Remove the initial 'v' from the ReleaseTag -$version = $ReleaseTag -replace '^v' -$semVersion = [System.Management.Automation.SemanticVersion] $version - -$metadata = Get-Content "$location/tools/metadata.json" -Raw | ConvertFrom-Json - -$LTS = $metadata.LTSRelease.Package - -Write-Verbose -Verbose -Message "LTS is set to: $LTS" - -function BuildPackages { - param( - [switch] $LTS - ) - - Push-Location - try { - Set-Location $location - Import-Module "$location/build.psm1" - Import-Module "$location/tools/packaging" - - Start-PSBootstrap -Package -NoSudo - - $buildParams = @{ Configuration = 'Release'; PSModuleRestore = $true; Restore = $true } - - if ($FxDependent.IsPresent) { - $projectAssetsZipName = 'linuxFxDependantProjectAssetssymbols.zip' - $buildParams.Add("Runtime", "fxdependent") - } elseif ($Alpine.IsPresent) { - $projectAssetsZipName = 'linuxAlpineProjectAssetssymbols.zip' - $buildParams.Add("Runtime", 'musl-x64') - } else { - # make the artifact name unique - $projectAssetsZipName = "linuxProjectAssets-$((Get-Date).Ticks)-symbols.zip" - } - - Start-PSBuild @buildParams @releaseTagParam - $options = Get-PSOptions - - if ($FxDependent) { - Start-PSPackage -Type 'fxdependent' @releaseTagParam -LTS:$LTS - } elseif ($Alpine) { - Start-PSPackage -Type 'tar-alpine' @releaseTagParam -LTS:$LTS - } else { - Start-PSPackage @releaseTagParam -LTS:$LTS - } - - if ($TarX64) { Start-PSPackage -Type tar @releaseTagParam -LTS:$LTS } - - if ($TarMinSize) { - Write-Verbose -Verbose "---- Min-Size ----" - Write-Verbose -Verbose "options.Output: $($options.Output)" - Write-Verbose -Verbose "options.Top $($options.Top)" - - $binDir = Join-Path -Path $options.Top -ChildPath 'bin' - Write-Verbose -Verbose "Remove $binDir, to get a clean build for min-size package" - Remove-Item -Path $binDir -Recurse -Force - - ## Build 'min-size' and create 'tar.gz' package for it. - $buildParams['ForMinimalSize'] = $true - Start-PSBuild @buildParams @releaseTagParam - Start-PSPackage -Type min-size @releaseTagParam -LTS:$LTS - } - - if ($TarArm) { - ## Build 'linux-arm' and create 'tar.gz' package for it. - ## Note that 'linux-arm' can only be built on Ubuntu environment. - Start-PSBuild -Configuration Release -Restore -Runtime linux-arm -PSModuleRestore @releaseTagParam - Start-PSPackage -Type tar-arm @releaseTagParam -LTS:$LTS - } - - if ($TarArm64) { - Start-PSBuild -Configuration Release -Restore -Runtime linux-arm64 -PSModuleRestore @releaseTagParam - Start-PSPackage -Type tar-arm64 @releaseTagParam -LTS:$LTS - } - } finally { - Pop-Location - } -} - -BuildPackages - -if ($LTS) { - Write-Verbose -Verbose "Packaging LTS" - BuildPackages -LTS -} - -$linuxPackages = Get-ChildItem "$location/powershell*" -Include *.deb,*.rpm,*.tar.gz - -foreach ($linuxPackage in $linuxPackages) -{ - $filePath = $linuxPackage.FullName - Write-Verbose "Copying $filePath to $destination" -Verbose - Copy-Item -Path $filePath -Destination $destination -Force -} - -Write-Verbose "Exporting project.assets files ..." -Verbose - -$projectAssetsCounter = 1 -$projectAssetsFolder = Join-Path -Path $destination -ChildPath 'projectAssets' -$projectAssetsZip = Join-Path -Path $destination -ChildPath $projectAssetsZipName -Get-ChildItem $location\project.assets.json -Recurse | ForEach-Object { - $subfolder = $_.FullName.Replace($location,'') - $subfolder.Replace('project.assets.json','') - $itemDestination = Join-Path -Path $projectAssetsFolder -ChildPath $subfolder - New-Item -Path $itemDestination -ItemType Directory -Force - $file = $_.FullName - Write-Verbose "Copying $file to $itemDestination" -Verbose - Copy-Item -Path $file -Destination "$itemDestination\" -Force - $projectAssetsCounter++ -} - -Compress-Archive -Path $projectAssetsFolder -DestinationPath $projectAssetsZip -Remove-Item -Path $projectAssetsFolder -Recurse -Force -ErrorAction SilentlyContinue diff --git a/tools/releaseBuild/Images/microsoft_powershell_windowsservercore/PowerShellPackage.ps1 b/tools/releaseBuild/Images/microsoft_powershell_windowsservercore/PowerShellPackage.ps1 deleted file mode 100644 index 41ec53fa495..00000000000 --- a/tools/releaseBuild/Images/microsoft_powershell_windowsservercore/PowerShellPackage.ps1 +++ /dev/null @@ -1,213 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -[cmdletbinding(DefaultParameterSetName='default')] -# PowerShell Script to clone, build and package PowerShell from specified fork and branch -param ( - [string] $fork = 'powershell', - - [string] $branch = 'master', - - [string] $location = "$PWD\powershell", - - [string] $destination = "$env:WORKSPACE", - - [ValidateSet("win7-x64", "win7-x86", "win-arm", "win-arm64", "fxdependent", "fxdependent-win-desktop")] - [string] $Runtime = 'win7-x64', - - [switch] $ForMinimalSize, - - [switch] $Wait, - - [ValidatePattern("^v\d+\.\d+\.\d+(-\w+(\.\d{1,2})?)?$")] - [ValidateNotNullOrEmpty()] - [string] $ReleaseTag, - - [Parameter(Mandatory,ParameterSetName='IncludeSymbols')] - [switch] $Symbols, - - [Parameter(Mandatory,ParameterSetName='packageSigned')] - [ValidatePattern("-signed.zip$")] - [string] $BuildZip, - - [Parameter(Mandatory,ParameterSetName='ComponentRegistration')] - [switch] $ComponentRegistration -) - -$releaseTagParam = @{} -if ($ReleaseTag) -{ - $releaseTagParam = @{ 'ReleaseTag' = $ReleaseTag } -} - -if (-not $env:homedrive) -{ - Write-Verbose "fixing empty home paths..." -Verbose - $profileParts = $env:userprofile -split ':' - $env:homedrive = $profileParts[0]+':' - $env:homepath = $profileParts[1] -} - -if (! (Test-Path $destination)) -{ - Write-Verbose "Creating destination $destination" -Verbose - $null = New-Item -Path $destination -ItemType Directory -} - -Write-Verbose "homedrive : ${env:homedrive}" -Write-Verbose "homepath : ${env:homepath}" - -# Don't use CIM_PhysicalMemory, docker containers may cache old values -$memoryMB = (Get-CimInstance win32_computersystem).TotalPhysicalMemory /1MB -$requiredMemoryMB = 2048 -if ($memoryMB -lt $requiredMemoryMB) -{ - throw "Building powershell requires at least $requiredMemoryMB MiB of memory and only $memoryMB MiB is present." -} -Write-Verbose "Running with $memoryMB MB memory." -Verbose - -try -{ - Set-Location $location - - Import-Module "$location\build.psm1" -Force - Import-Module "$location\tools\packaging" -Force - $env:platform = $null - - Write-Verbose "Sync'ing Tags..." -Verbose - Sync-PSTags -AddRemoteIfMissing - - Write-Verbose "Bootstrapping powershell build..." -Verbose - Start-PSBootstrap -Force -Package -ErrorAction Stop - - if ($PSCmdlet.ParameterSetName -eq 'packageSigned') - { - Write-Verbose "Expanding signed build..." -Verbose - if($Runtime -like 'fxdependent*') - { - Expand-PSSignedBuild -BuildZip $BuildZip -SkipPwshExeCheck - } - else - { - Expand-PSSignedBuild -BuildZip $BuildZip - } - - Remove-Item -Path $BuildZip - } - else - { - Write-Verbose "Starting powershell build for RID: $Runtime and ReleaseTag: $ReleaseTag ..." -Verbose - $buildParams = @{ - ForMinimalSize = $ForMinimalSize - } - - if($Symbols) - { - $buildParams['NoPSModuleRestore'] = $true - } - else - { - $buildParams['PSModuleRestore'] = $true - } - - Start-PSBuild -Clean -Runtime $Runtime -Configuration Release @releaseTagParam @buildParams - } - - if ($ComponentRegistration) - { - Write-Verbose "Exporting project.assets files ..." -Verbose - - $projectAssetsCounter = 1 - $projectAssetsFolder = Join-Path -Path $destination -ChildPath 'projectAssets' - $projectAssetsZip = Join-Path -Path $destination -ChildPath 'windowsProjectAssetssymbols.zip' - Get-ChildItem $location\project.assets.json -Recurse | ForEach-Object { - $subfolder = $_.FullName.Replace($location,'') - $subfolder.Replace('project.assets.json','') - $itemDestination = Join-Path -Path $projectAssetsFolder -ChildPath $subfolder - New-Item -Path $itemDestination -ItemType Directory -Force > $null - $file = $_.FullName - Write-Verbose "Copying $file to $itemDestination" -Verbose - Copy-Item -Path $file -Destination "$itemDestination\" -Force - $projectAssetsCounter++ - } - - Compress-Archive -Path $projectAssetsFolder -DestinationPath $projectAssetsZip - Remove-Item -Path $projectAssetsFolder -Recurse -Force -ErrorAction SilentlyContinue - - return - } - - if ($Runtime -like 'fxdependent*') - { - $pspackageParams = @{'Type' = $Runtime} - } - else - { - ## Set the default package type. - $pspackageParams = @{'Type' = 'msi'; 'WindowsRuntime' = $Runtime} - if ($ForMinimalSize) - { - ## Special case for the minimal size self-contained package. - $pspackageParams['Type'] = 'min-size' - } - } - - if (!$Symbols -and $Runtime -notlike 'fxdependent*' -and !$ForMinimalSize) - { - Write-Verbose "Starting powershell packaging(msi)..." -Verbose - Start-PSPackage @pspackageParams @releaseTagParam - - $pspackageParams['Type']='msix' - Write-Verbose "Starting powershell packaging(msix)..." -Verbose - Start-PSPackage @pspackageParams @releaseTagParam - } - - if ($Runtime -like 'fxdependent*' -or $ForMinimalSize) - { - ## Add symbols for just like zip package. - $pspackageParams['IncludeSymbols']=$Symbols - Start-PSPackage @pspackageParams @releaseTagParam - - ## Copy the fxdependent Zip package to destination. - Get-ChildItem $location\PowerShell-*.zip | ForEach-Object { - $file = $_.FullName - Write-Verbose "Copying $file to $destination" -Verbose - Copy-Item -Path $file -Destination "$destination\" -Force - } - } - else - { - if (!$Symbols) { - $pspackageParams['Type'] = 'zip-pdb' - Write-Verbose "Starting powershell symbols packaging(zip)..." -Verbose - Start-PSPackage @pspackageParams @releaseTagParam - } - - $pspackageParams['Type']='zip' - $pspackageParams['IncludeSymbols']=$Symbols - Write-Verbose "Starting powershell packaging(zip)..." -Verbose - Start-PSPackage @pspackageParams @releaseTagParam - - Write-Verbose "Exporting packages ..." -Verbose - - Get-ChildItem $location\*.msi,$location\*.zip,$location\*.wixpdb,$location\*.msix,$location\*.exe | ForEach-Object { - $file = $_.FullName - Write-Verbose "Copying $file to $destination" -Verbose - Copy-Item -Path $file -Destination "$destination\" -Force - } - } -} -finally -{ - Write-Verbose "Beginning build clean-up..." -Verbose - if ($Wait) - { - $path = Join-Path $PSScriptRoot -ChildPath 'delete-to-continue.txt' - $null = New-Item -Path $path -ItemType File - Write-Verbose "Computer name: $env:COMPUTERNAME" -Verbose - Write-Verbose "Delete $path to exit." -Verbose - while(Test-Path -LiteralPath $path) - { - Start-Sleep -Seconds 60 - } - } -} diff --git a/tools/releaseBuild/Images/microsoft_powershell_windowsservercore/dockerInstall.psm1 b/tools/releaseBuild/Images/microsoft_powershell_windowsservercore/dockerInstall.psm1 deleted file mode 100644 index 311fed7e169..00000000000 --- a/tools/releaseBuild/Images/microsoft_powershell_windowsservercore/dockerInstall.psm1 +++ /dev/null @@ -1,115 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -function Install-ChocolateyPackage -{ - param( - [Parameter(Mandatory=$true)] - [string] - $PackageName, - - [Parameter(Mandatory=$false)] - [string] - $Executable, - - [string[]] - $ArgumentList, - - [switch] - $Cleanup, - - [int] - $ExecutionTimeout = 2700, - - [string] - $Version - ) - - if(-not(Get-Command -Name Choco -ErrorAction SilentlyContinue)) - { - Write-Verbose "Installing Chocolatey provider..." -Verbose - Invoke-WebRequest https://chocolatey.org/install.ps1 -UseBasicParsing | Invoke-Expression - } - - Write-Verbose "Installing $PackageName..." -Verbose - $extraCommand = @() - if($Version) - { - $extraCommand += '--version', $version - } - choco install -y $PackageName --no-progress --execution-timeout=$ExecutionTimeout $ArgumentList $extraCommands - - if($executable) - { - Write-Verbose "Verifing $Executable is in path..." -Verbose - $exeSource = $null - $exeSource = Get-ChildItem -Path "$env:ProgramFiles\$Executable" -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName - if(!$exeSource) - { - Write-Verbose "Falling back to x86 program files..." -Verbose - $exeSource = Get-ChildItem -Path "${env:ProgramFiles(x86)}\$Executable" -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName - } - - # Don't search the chocolatey program data until more official locations have been searched - if(!$exeSource) - { - Write-Verbose "Falling back to chocolatey..." -Verbose - $exeSource = Get-ChildItem -Path "$env:ProgramData\chocolatey\$Executable" -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName - } - - # all obvious locations are exhausted, use brute force and search from the root of the filesystem - if(!$exeSource) - { - Write-Verbose "Falling back to the root of the drive..." -Verbose - $exeSource = Get-ChildItem -Path "/$Executable" -Recurse -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName - } - - if(!$exeSource) - { - throw "$Executable not found" - } - - $exePath = Split-Path -Path $exeSource - Append-Path -path $exePath - } - - if($Cleanup.IsPresent) - { - Remove-Folder -Folder "$env:temp\chocolatey" - } -} - -function Append-Path -{ - param - ( - $path - ) - $machinePathString = [System.Environment]::GetEnvironmentVariable('path',[System.EnvironmentVariableTarget]::Machine) - $machinePath = $machinePathString -split ';' - - if($machinePath -inotcontains $path) - { - $newPath = "$machinePathString;$path" - Write-Verbose "Adding $path to path..." -Verbose - [System.Environment]::SetEnvironmentVariable('path',$newPath,[System.EnvironmentVariableTarget]::Machine) - Write-Verbose "Added $path to path." -Verbose - } - else - { - Write-Verbose "$path already in path." -Verbose - } -} - -function Remove-Folder -{ - param( - [string] - $Folder - ) - - Write-Verbose "Cleaning up $Folder..." -Verbose - $filter = Join-Path -Path $Folder -ChildPath * - [int]$measuredCleanupMB = (Get-ChildItem $filter -Recurse | Measure-Object -Property Length -Sum).Sum / 1MB - Remove-Item -Recurse -Force $filter -ErrorAction SilentlyContinue - Write-Verbose "Cleaned up $measuredCleanupMB MB from $Folder" -Verbose -} diff --git a/tools/releaseBuild/README.md b/tools/releaseBuild/README.md deleted file mode 100644 index 9b78e742b5f..00000000000 --- a/tools/releaseBuild/README.md +++ /dev/null @@ -1,55 +0,0 @@ -# Azure Dev Ops Release Builds - -## Requirements - -Docker must be installed to run any of the release builds. - -## Running Windows Release Builds Locally - -From PowerShell on Windows, run `.\vstsbuild.ps1 -ReleaseTag <tag> -Name <buildName>`. - -For the package builds, run `.\vstsbuild.ps1 -ReleaseTag <tag> -Name <buildName> -BuildPath <path to extracted zip from build step> -SignedFilesPath <path to extracted 'symbol' zip from build step>` - -Windows Build Names: - -* `win7-x64-symbols` - * Builds the Windows x64 Zip with symbols -* `win7-x86-symbols` - * Builds the Windows x86 Zip with symbols -* `win7-arm-symbols` - * Builds the Windows ARM Zip with symbols -* `win7-arm64-symbols` - * Builds the Windows ARM64 Zip with symbols -* `win7-fxdependent-symbols` - * Builds the Windows FxDependent Zip with symbols -* `win7-x64-package` - * Builds the Windows x64 packages -* `win7-x86-package` - * Builds the Windows x86 packages -* `win7-arm-package` - * Builds the Windows ARM packages -* `win7-arm64-package` - * Builds the Windows ARM64 packages -* `win7-fxdependent-package` - * Builds the Windows FxDependent packages - -## Running Linux Release Builds Locally - -From PowerShell on Linux or macOS, run `.\vstsbuild.ps1 -ReleaseTag <tag> -Name <buildName>`. - -Linux Build Names: - -* `deb` - * Builds the Debian Packages, ARM32 and ARM64. -* `alpine` - * Builds the Alpine Package -* `rpm` - * Builds the RedHat variant Package - -## Azure Dev Ops Build - -The release build is fairly complicated. The definition is at `./azureDevOps/releaseBuild.yml`. - -Here is a diagram of the build: - -[![Release Build diagram](https://raw.githubusercontent.com/PowerShell/PowerShell/master/tools/releaseBuild/azureDevOps/diagram.svg?sanitize=true)](https://raw.githubusercontent.com/PowerShell/PowerShell/master/tools/releaseBuild/azureDevOps/diagram.svg?sanitize=true) diff --git a/tools/releaseBuild/azureDevOps/AzArtifactFeed/PSGalleryToAzArtifacts.yml b/tools/releaseBuild/azureDevOps/AzArtifactFeed/PSGalleryToAzArtifacts.yml deleted file mode 100644 index da26ea6d348..00000000000 --- a/tools/releaseBuild/azureDevOps/AzArtifactFeed/PSGalleryToAzArtifacts.yml +++ /dev/null @@ -1,33 +0,0 @@ -# Sync packages from PSGallery to Azure DevOps Artifacts feed - -resources: -- repo: self - clean: true - -pool: - name: 1es - demands: - - ImageOverride -equals PSMMS2019-Minimal - -steps: - - pwsh: | - $minVer = [version]"2.2.3" - $curVer = Get-Module PowerShellGet -ListAvailable | Select-Object -First 1 | ForEach-Object Version - if (-not $curVer -or $curVer -lt $minVer) { - Install-Module -Name PowerShellGet -MinimumVersion 2.2.3 -Force - } - displayName: Update PSGet and PackageManagement - condition: succeededOrFailed() - - - pwsh: | - Write-Verbose -Verbose "Packages to upload" - if(Test-Path $(Build.ArtifactStagingDirectory)) { Get-ChildItem "$(Build.ArtifactStagingDirectory)/*.nupkg" | ForEach-Object { $_.FullName }} - displayName: List packages to upload - condition: succeededOrFailed() - - - task: NuGetCommand@2 - displayName: 'NuGet push' - inputs: - command: push - publishVstsFeed: 'pscore-release' - publishFeedCredentials: 'AzArtifactsFeed' diff --git a/tools/releaseBuild/azureDevOps/compliance.yml b/tools/releaseBuild/azureDevOps/compliance.yml deleted file mode 100644 index 3624f1e1081..00000000000 --- a/tools/releaseBuild/azureDevOps/compliance.yml +++ /dev/null @@ -1,67 +0,0 @@ -name: Compliance-$(Build.BuildId) - -trigger: none -pr: none - -schedules: - # Chrontab format, see https://en.wikipedia.org/wiki/Cron - # this is in UTC - - cron: '0 13 * * *' - branches: - include: - - master - -resources: - repositories: - - repository: ComplianceRepo - type: github - endpoint: ComplianceGHRepo - name: PowerShell/compliance - ref: master - -parameters: -- name: InternalSDKBlobURL - displayName: URL to the blob havibg internal .NET SDK - type: string - default: ' ' - -variables: - - name: DOTNET_CLI_TELEMETRY_OPTOUT - value: 1 - - name: POWERSHELL_TELEMETRY_OPTOUT - value: 1 - - name: nugetMultiFeedWarnLevel - value: none - - name: NugetSecurityAnalysisWarningLevel - value: none - # Defines the variables AzureFileCopySubscription, StorageAccount, StorageAccountKey, StorageResourceGroup, StorageSubscriptionName - - group: 'Azure Blob variable group' - # Defines the variables CgPat, CgOrganization, and CgProject - - group: 'ComponentGovernance' - - group: 'PoolNames' - - name: __DOTNET_RUNTIME_FEED - value: ${{ parameters.InternalSDKBlobURL }} - - -stages: - - stage: compliance - displayName: 'Compliance' - dependsOn: [] - jobs: - - template: templates/compliance/compliance.yml - parameters: - parentJobs: [] - - stage: APIScan - displayName: 'ApiScan' - dependsOn: [] - jobs: - - template: templates/compliance/apiscan.yml - parameters: - parentJobs: [] - - stage: notice - displayName: Generate Notice File - dependsOn: [] - jobs: - - template: templates/compliance/generateNotice.yml - parameters: - parentJobs: [] diff --git a/tools/releaseBuild/azureDevOps/diagram.puml b/tools/releaseBuild/azureDevOps/diagram.puml deleted file mode 100644 index ade53b11b9c..00000000000 --- a/tools/releaseBuild/azureDevOps/diagram.puml +++ /dev/null @@ -1,107 +0,0 @@ -@startuml - -folder "Linux Builds" as LinuxBuilds { - ' Define the build tasks as business processes - agent "DEB" as BuildDEB - agent "RPM" as BuildRPM - agent "Alpine" as BuildAlpine - agent "Linux-FxDependent" as BuildLinuxFx - -} - -agent "macOS Build" as BuildMac - -agent "Upload build metadata" as BuildMetadata - -folder "Windows Builds" as WinBuilds { - agent "x64" as BuildWinX64 - agent "x86" as BuildWinX86 - agent "arm32" as BuildWinArm32 - agent "arm64" as BuildWinArm64 - agent "FxDependent" as BuildWinFx -} - -agent "ComponentRegistration" as BuildCG - -folder "Linux Package Scanning and Upload" as PkgScanUploadLinux { - agent "DEB" as UploadDEB - agent "RPM" as UploadRPM - agent "Alpine" as UploadAlpine - agent "Linux-FxDependent" as UploadLinuxFx -} - -folder "Package Signing and Upload" as PkgSignUpload { - agent "macOS" as SignMac - - agent "Windows" as SignWin -} - -folder "Build Test Artifacts" as TestArtifacts { - agent "Windows" as WinTest - agent "Linux" as LinuxTest - agent "Linux-ARM" as LinuxArmTest - agent "Linux-ARM64" as LinuxArm64Test -} - -agent "Compliance" as Compliance - - -agent "Create SDK and Global Tool and Upload" as BuildNuGet - - -' Define finishing the build as a goal filled -control "Finish" as Finish -control "Start" as Start - -' map the various Upload task dependencies -BuildDEB -down-> UploadDEB -BuildRPM -down-> UploadRPM -BuildLinuxFx -down-> UploadLinuxFx -BuildAlpine -down-> UploadAlpine - -' map all of the SignMac task dependencies -BuildMac -down-> SignMac - -' map all of the SignWin task dependencies -WinBuilds -down-> SignWin -'BuildWinX64 -down-> SignWin -'BuildWinX86 -down-> SignWin -'BuildWinArm32 -down-> SignWin -'BuildWinArm64 -down-> SignWin -'BuildWinFx -down-> SignWin - -' map all of the Compliance task dependencies -BuildWinX86 -down-> Compliance -BuildWinX64 -down-> Compliance -BuildWinFx -down-> Compliance - -PkgSignUpload -down-> BuildNuGet -LinuxBuilds -down-> BuildNuGet - -' map all leafs to finish -Compliance ~~ Finish -UploadAlpine ~~ Finish -UploadDEB ~~ Finish -UploadRPM ~~ Finish -UploadLinuxFx ~~ Finish -SignMac ~~ Finish -BuildCG ~~ Finish -BuildNuGet ~~ Finish -TestArtifacts ~~ Finish -BuildMetadata ~~ Finish - -Start ~~ BuildDEB -Start ~~ BuildRPM -Start ~~ BuildAlpine -Start ~~ BuildLinuxFx -Start ~~ BuildMac -Start ~~ BuildWinX64 -Start ~~ BuildWinX86 -Start ~~ BuildWinFx -Start ~~ BuildWinArm32 -Start ~~ BuildWinArm64 -Start ~~ BuildCG -Start ~~ TestArtifacts -Start ~~ BuildMetadata - -@enduml diff --git a/tools/releaseBuild/azureDevOps/diagram.svg b/tools/releaseBuild/azureDevOps/diagram.svg deleted file mode 100644 index 024128bf988..00000000000 --- a/tools/releaseBuild/azureDevOps/diagram.svg +++ /dev/null @@ -1,108 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentScriptType="application/ecmascript" contentStyleType="text/css" height="560px" preserveAspectRatio="none" style="width:1693px;height:560px;" version="1.1" viewBox="0 0 1693 560" width="1693px" zoomAndPan="magnify"><defs><filter height="300%" id="f1l08wivdvpjam" width="300%" x="-1" y="-1"><feGaussianBlur result="blurOut" stdDeviation="2.0"/><feColorMatrix in="blurOut" result="blurOut2" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .4 0"/><feOffset dx="4.0" dy="4.0" in="blurOut2" result="blurOut3"/><feBlend in="SourceGraphic" in2="blurOut3" mode="normal"/></filter></defs><g><!--cluster LinuxBuilds--><polygon fill="#FFFFFF" filter="url(#f1l08wivdvpjam)" points="1198,228,1290,228,1297,251.6094,1671,251.6094,1671,334,1198,334,1198,228" style="stroke: #000000; stroke-width: 1.5;"/><line style="stroke: #000000; stroke-width: 1.5;" x1="1198" x2="1297" y1="251.6094" y2="251.6094"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="86" x="1202" y="244.5332">Linux Builds</text><!--cluster WinBuilds--><polygon fill="#FFFFFF" filter="url(#f1l08wivdvpjam)" points="570,82,687,82,694,105.6094,1087,105.6094,1087,188,570,188,570,82" style="stroke: #000000; stroke-width: 1.5;"/><line style="stroke: #000000; stroke-width: 1.5;" x1="570" x2="694" y1="105.6094" y2="105.6094"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="111" x="574" y="98.5332">Windows Builds</text><!--cluster PkgScanUploadLinux--><polygon fill="#FFFFFF" filter="url(#f1l08wivdvpjam)" points="1225,366,1484,366,1491,389.6094,1663,389.6094,1663,456,1225,456,1225,366" style="stroke: #000000; stroke-width: 1.5;"/><line style="stroke: #000000; stroke-width: 1.5;" x1="1225" x2="1491" y1="389.6094" y2="389.6094"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="253" x="1229" y="382.5332">Linux Package Scanning and Upload</text><!--cluster PkgSignUpload--><polygon fill="#FFFFFF" filter="url(#f1l08wivdvpjam)" points="904,228,1109,228,1116,251.6094,1148,251.6094,1148,334,904,334,904,228" style="stroke: #000000; stroke-width: 1.5;"/><line style="stroke: #000000; stroke-width: 1.5;" x1="904" x2="1116" y1="251.6094" y2="251.6094"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="199" x="908" y="244.5332">Package Signing and Upload</text><!--cluster TestArtifacts--><polygon fill="#FFFFFF" filter="url(#f1l08wivdvpjam)" points="30,82,168,82,175,105.6094,313,105.6094,313,334,30,334,30,82" style="stroke: #000000; stroke-width: 1.5;"/><line style="stroke: #000000; stroke-width: 1.5;" x1="30" x2="175" y1="105.6094" y2="105.6094"/><text fill="#000000" font-family="sans-serif" font-size="14" font-weight="bold" lengthAdjust="spacingAndGlyphs" textLength="132" x="34" y="98.5332">Build Test Artifacts</text><!--entity BuildDEB--><rect fill="#FEFECE" filter="url(#f1l08wivdvpjam)" height="37.6094" style="stroke: #A80036; stroke-width: 1.5;" width="48" x="1241" y="272"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="28" x="1251" y="296.5332">DEB</text><!--entity BuildRPM--><rect fill="#FEFECE" filter="url(#f1l08wivdvpjam)" height="37.6094" style="stroke: #A80036; stroke-width: 1.5;" width="50" x="1324" y="272"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="30" x="1334" y="296.5332">RPM</text><!--entity BuildAlpine--><rect fill="#FEFECE" filter="url(#f1l08wivdvpjam)" height="37.6094" style="stroke: #A80036; stroke-width: 1.5;" width="59" x="1409.5" y="272"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="39" x="1419.5" y="296.5332">Alpine</text><!--entity BuildLinuxFx--><rect fill="#FEFECE" filter="url(#f1l08wivdvpjam)" height="37.6094" style="stroke: #A80036; stroke-width: 1.5;" width="143" x="1503.5" y="272"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="123" x="1513.5" y="296.5332">Linux-FxDependent</text><!--entity BuildWinX64--><rect fill="#FEFECE" filter="url(#f1l08wivdvpjam)" height="37.6094" style="stroke: #A80036; stroke-width: 1.5;" width="42" x="1002" y="126"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="22" x="1012" y="150.5332">x64</text><!--entity BuildWinX86--><rect fill="#FEFECE" filter="url(#f1l08wivdvpjam)" height="37.6094" style="stroke: #A80036; stroke-width: 1.5;" width="42" x="925" y="126"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="22" x="935" y="150.5332">x86</text><!--entity BuildWinArm32--><rect fill="#FEFECE" filter="url(#f1l08wivdvpjam)" height="37.6094" style="stroke: #A80036; stroke-width: 1.5;" width="60" x="830" y="126"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="40" x="840" y="150.5332">arm32</text><!--entity BuildWinArm64--><rect fill="#FEFECE" filter="url(#f1l08wivdvpjam)" height="37.6094" style="stroke: #A80036; stroke-width: 1.5;" width="60" x="735" y="126"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="40" x="745" y="150.5332">arm64</text><!--entity BuildWinFx--><rect fill="#FEFECE" filter="url(#f1l08wivdvpjam)" height="37.6094" style="stroke: #A80036; stroke-width: 1.5;" width="105" x="594.5" y="126"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="85" x="604.5" y="150.5332">FxDependent</text><!--entity UploadDEB--><rect fill="#FEFECE" filter="url(#f1l08wivdvpjam)" height="37.6094" style="stroke: #A80036; stroke-width: 1.5;" width="48" x="1241" y="402"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="28" x="1251" y="426.5332">DEB</text><!--entity UploadRPM--><rect fill="#FEFECE" filter="url(#f1l08wivdvpjam)" height="37.6094" style="stroke: #A80036; stroke-width: 1.5;" width="50" x="1324" y="402"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="30" x="1334" y="426.5332">RPM</text><!--entity UploadAlpine--><rect fill="#FEFECE" filter="url(#f1l08wivdvpjam)" height="37.6094" style="stroke: #A80036; stroke-width: 1.5;" width="59" x="1409.5" y="402"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="39" x="1419.5" y="426.5332">Alpine</text><!--entity UploadLinuxFx--><rect fill="#FEFECE" filter="url(#f1l08wivdvpjam)" height="37.6094" style="stroke: #A80036; stroke-width: 1.5;" width="143" x="1503.5" y="402"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="123" x="1513.5" y="426.5332">Linux-FxDependent</text><!--entity SignMac--><rect fill="#FEFECE" filter="url(#f1l08wivdvpjam)" height="37.6094" style="stroke: #A80036; stroke-width: 1.5;" width="66" x="1039" y="272"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="46" x="1049" y="296.5332">macOS</text><!--entity SignWin--><rect fill="#FEFECE" filter="url(#f1l08wivdvpjam)" height="37.6094" style="stroke: #A80036; stroke-width: 1.5;" width="76" x="928" y="272"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="56" x="938" y="296.5332">Windows</text><!--entity WinTest--><rect fill="#FEFECE" filter="url(#f1l08wivdvpjam)" height="37.6094" style="stroke: #A80036; stroke-width: 1.5;" width="76" x="60" y="126"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="56" x="70" y="150.5332">Windows</text><!--entity LinuxTest--><rect fill="#FEFECE" filter="url(#f1l08wivdvpjam)" height="37.6094" style="stroke: #A80036; stroke-width: 1.5;" width="53" x="171.5" y="126"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="33" x="181.5" y="150.5332">Linux</text><!--entity LinuxArmTest--><rect fill="#FEFECE" filter="url(#f1l08wivdvpjam)" height="37.6094" style="stroke: #A80036; stroke-width: 1.5;" width="88" x="54" y="272"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="68" x="64" y="296.5332">Linux-ARM</text><!--entity LinuxArm64Test--><rect fill="#FEFECE" filter="url(#f1l08wivdvpjam)" height="37.6094" style="stroke: #A80036; stroke-width: 1.5;" width="104" x="177" y="272"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="84" x="187" y="296.5332">Linux-ARM64</text><!--entity BuildMac--><rect fill="#FEFECE" filter="url(#f1l08wivdvpjam)" height="37.6094" style="stroke: #A80036; stroke-width: 1.5;" width="101" x="1114.5" y="126"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="81" x="1124.5" y="150.5332">macOS Build</text><!--entity BuildMetadata--><rect fill="#FEFECE" filter="url(#f1l08wivdvpjam)" height="37.6094" style="stroke: #A80036; stroke-width: 1.5;" width="161" x="387.5" y="402"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="141" x="397.5" y="426.5332">Upload build metadata</text><!--entity BuildCG--><rect fill="#FEFECE" filter="url(#f1l08wivdvpjam)" height="37.6094" style="stroke: #A80036; stroke-width: 1.5;" width="169" x="583.5" y="402"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="149" x="593.5" y="426.5332">ComponentRegistration</text><!--entity Compliance--><rect fill="#FEFECE" filter="url(#f1l08wivdvpjam)" height="37.6094" style="stroke: #A80036; stroke-width: 1.5;" width="94" x="777" y="272"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="74" x="787" y="296.5332">Compliance</text><!--entity BuildNuGet--><rect fill="#FEFECE" filter="url(#f1l08wivdvpjam)" height="37.6094" style="stroke: #A80036; stroke-width: 1.5;" width="276" x="930" y="402"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="256" x="940" y="426.5332">Create SDK and Global Tool and Upload</text><!--entity Finish--><ellipse cx="972" cy="516" fill="#FEFECE" filter="url(#f1l08wivdvpjam)" rx="12" ry="12" style="stroke: #A80036; stroke-width: 2.0;"/><polygon fill="#A80036" points="968,504,974,499,972,504,974,509,968,504" style="stroke: #A80036; stroke-width: 1.0;"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="38" x="953" y="546.5332">Finish</text><!--entity Start--><ellipse cx="946" cy="24" fill="#FEFECE" filter="url(#f1l08wivdvpjam)" rx="12" ry="12" style="stroke: #A80036; stroke-width: 2.0;"/><polygon fill="#A80036" points="942,12,948,7,946,12,948,17,942,12" style="stroke: #A80036; stroke-width: 1.0;"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacingAndGlyphs" textLength="30" x="931" y="54.5332">Start</text><!--link BuildDEB to UploadDEB--><path d="M1265,310.068 C1265,332.714 1265,371.578 1265,396.511 " fill="none" id="BuildDEB-UploadDEB" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="1265,401.61,1269,392.61,1265,396.61,1261,392.61,1265,401.61" style="stroke: #A80036; stroke-width: 1.0;"/><!--link BuildRPM to UploadRPM--><path d="M1349,310.068 C1349,332.714 1349,371.578 1349,396.511 " fill="none" id="BuildRPM-UploadRPM" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="1349,401.61,1353,392.61,1349,396.61,1345,392.61,1349,401.61" style="stroke: #A80036; stroke-width: 1.0;"/><!--link BuildLinuxFx to UploadLinuxFx--><path d="M1575,310.068 C1575,332.714 1575,371.578 1575,396.511 " fill="none" id="BuildLinuxFx-UploadLinuxFx" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="1575,401.61,1579,392.61,1575,396.61,1571,392.61,1575,401.61" style="stroke: #A80036; stroke-width: 1.0;"/><!--link BuildAlpine to UploadAlpine--><path d="M1439,310.068 C1439,332.714 1439,371.578 1439,396.511 " fill="none" id="BuildAlpine-UploadAlpine" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="1439,401.61,1443,392.61,1439,396.61,1435,392.61,1439,401.61" style="stroke: #A80036; stroke-width: 1.0;"/><!--link BuildMac to SignMac--><path d="M1147.44,164.036 C1135.64,176.76 1120.25,194.578 1109,212 C1097.65,229.574 1087.68,251.168 1080.96,267.177 " fill="none" id="BuildMac-SignMac" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="1078.98,271.955,1086.1234,265.1748,1080.896,267.3367,1078.7341,262.1092,1078.98,271.955" style="stroke: #A80036; stroke-width: 1.0;"/><!--link WinBuilds to SignWin--><path d="M1071.1227,188.1926 C1071.0799,188.2871 1071.037,188.3816 1070.9938,188.4759 C1070.9074,188.6645 1070.8201,188.8527 1070.7319,189.0404 C1070.5554,189.4159 1070.3752,189.7896 1070.1912,190.1613 C1069.8231,190.9047 1069.4397,191.6401 1069.0405,192.3656 C1068.242,193.8167 1067.3799,195.2288 1066.4491,196.588 C1064.5875,199.3065 1062.4513,201.8138 1060,204 C1046.79,215.777 1035.81,202.311 1021,212 C1000.596,225.353 985.476,249.13 976.307,266.933 " fill="none" id="WinBuilds-SignWin" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="973.879,271.783,981.4848,265.5259,976.1174,267.312,974.3312,261.9445,973.879,271.783" style="stroke: #A80036; stroke-width: 1.0;"/><!--link BuildWinX86 to Compliance--><path d="M935,164.085 C927.58,175.828 917.298,191.261 907,204 C888.324,227.104 864.577,251.264 847.273,268.073 " fill="none" id="BuildWinX86-Compliance" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="843.462,271.757,852.7137,268.3798,847.0577,268.2827,847.1549,262.6267,843.462,271.757" style="stroke: #A80036; stroke-width: 1.0;"/><!--link BuildWinX64 to Compliance--><path d="M1015.85,164.114 C1009.56,177.486 999.048,194.883 984,204 C956.463,220.684 941.426,198.932 912,212 C883.726,224.557 858.09,249.579 841.876,267.909 " fill="none" id="BuildWinX64-Compliance" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="838.362,271.945,847.2889,267.7845,841.6455,268.1742,841.2557,262.5308,838.362,271.945" style="stroke: #A80036; stroke-width: 1.0;"/><!--link BuildWinFx to Compliance--><path d="M668.688,164.11 C682.479,175.592 700.737,190.728 717,204 C744.291,226.271 775.735,251.493 797.44,268.834 " fill="none" id="BuildWinFx-Compliance" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="801.377,271.977,796.8417,263.2345,797.4704,268.8563,791.8486,269.485,801.377,271.977" style="stroke: #A80036; stroke-width: 1.0;"/><!--link PkgSignUpload to BuildNuGet--><path d="M1128.0529,334.0996 C1128.015,334.199 1127.9771,334.2984 1127.939,334.3978 C1127.863,334.5965 1127.7865,334.7952 1127.7098,334.9938 C1127.5562,335.391 1127.4012,335.788 1127.2448,336.1844 C1126.6191,337.7703 1125.9706,339.349 1125.2994,340.9108 C1123.957,344.0343 1122.5238,347.09 1121,350 C1111.97,367.256 1098.75,384.726 1087.77,397.897 " fill="none" id="PkgSignUpload-BuildNuGet" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="1084.44,401.841,1093.3049,397.55,1087.6679,398.0225,1087.1953,392.3854,1084.44,401.841" style="stroke: #A80036; stroke-width: 1.0;"/><!--link LinuxBuilds to BuildNuGet--><path d="M1197.9782,302.4489 C1197.9236,302.5184 1197.8687,302.5882 1197.8136,302.6583 C1197.5931,302.9385 1197.3689,303.223 1197.1411,303.5117 C1196.6855,304.0889 1196.2153,304.6826 1195.7313,305.2915 C1193.7955,307.7271 1191.6391,310.4055 1189.3088,313.2465 C1179.9875,324.6105 1167.885,338.5755 1156,350 C1137.71,367.585 1115.3,385.359 1097.68,398.597 " fill="none" id="LinuxBuilds-BuildNuGet" style="stroke: #A80036; stroke-width: 1.0;"/><polygon fill="#A80036" points="1093.31,401.862,1102.9165,399.6906,1097.3189,398.8739,1098.1356,393.2763,1093.31,401.862" style="stroke: #A80036; stroke-width: 1.0;"/><!--link Compliance to Finish--><path d="M826.637,310.083 C832.268,343.487 848.092,415.843 885,464 C903.081,487.5915 933.118,505.3521 952.738,515.2143 " fill="none" id="Compliance-Finish" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 1.0,3.0;"/><!--link UploadAlpine to Finish--><path d="M1422.98,440.271 C1414.38,448.879 1403.08,458.4543 1391,464 C1317.88,497.5828 1063.23,517.6171 991.145,522.7068 " fill="none" id="UploadAlpine-Finish" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 1.0,3.0;"/><!--link UploadDEB to Finish--><path d="M1250.8,440.277 C1243.37,448.672 1233.62,458.0596 1223,464 C1144.6,507.8556 1035.04,519.8199 991.222,522.9464 " fill="none" id="UploadDEB-Finish" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 1.0,3.0;"/><!--link UploadRPM to Finish--><path d="M1334.91,440.111 C1327.28,448.678 1317.13,458.2653 1306,464 C1197.06,520.1182 1044.66,524.6537 991.334,524.3816 " fill="none" id="UploadRPM-Finish" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 1.0,3.0;"/><!--link UploadLinuxFx to Finish--><path d="M1543.02,440.193 C1526.5,448.781 1505.71,458.3623 1486,464 C1298.56,517.6086 1059.57,523.4451 991.102,523.9793 " fill="none" id="UploadLinuxFx-Finish" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 1.0,3.0;"/><!--link SignMac to Finish--><path d="M1061.55,310.096 C1052.65,323.645 1038.53,341.285 1021,350 C999.252,360.813 928.533,340.209 912,358 C879.93,392.51 893.971,420.475 912,464 C920.231,483.8706 938.589,500.5917 952.845,511.2926 " fill="none" id="SignMac-Finish" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 1.0,3.0;"/><!--link BuildCG to Finish--><path d="M714.381,440.056 C734.463,447.702 758.297,456.5462 780,464 C842.756,485.5534 918.12,508.164 952.809,518.39 " fill="none" id="BuildCG-Finish" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 1.0,3.0;"/><!--link BuildNuGet to Finish--><path d="M1050.84,440.236 C1034.12,457.9993 1008.74,484.9597 991.324,503.4681 " fill="none" id="BuildNuGet-Finish" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 1.0,3.0;"/><!--link TestArtifacts to Finish--><path d="M313.0986,250.1395 C313.1559,250.6792 313.2135,251.2198 313.2715,251.7614 C313.3875,252.8445 313.5049,253.9315 313.6238,255.0222 C313.8616,257.2034 314.1053,259.3992 314.355,261.6076 C315.3538,270.4413 316.4487,279.4776 317.647,288.5995 C327.2335,361.5745 343.438,440.0245 370,464 C414.344,504.0257 855.296,520.2962 952.927,523.4229 " fill="none" id="TestArtifacts-Finish" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 1.0,3.0;"/><!--link BuildMetadata to Finish--><path d="M503.949,440.057 C522.139,448.504 544.805,458.0014 566,464 C711.002,505.0383 893.53,519.2208 952.606,522.9114 " fill="none" id="BuildMetadata-Finish" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 1.0,3.0;"/><!--link Start to BuildDEB--><path d="M962.193,34.666 C1019.95,37.193 1213.43,47.023 1233,66 C1290.11,121.375 1276.89,229.303 1268.87,271.995 " fill="none" id="Start-BuildDEB" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 1.0,3.0;"/><!--link Start to BuildRPM--><path d="M962.101,34.499 C1025.55,36.604 1256.07,45.608 1281,66 C1344.86,118.238 1350.2,228.481 1349.67,271.889 " fill="none" id="Start-BuildRPM" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 1.0,3.0;"/><!--link Start to BuildAlpine--><path d="M962.398,34.353 C1032.99,36.004 1308.81,43.837 1340,66 C1410.53,116.121 1431.49,228.103 1437.12,271.926 " fill="none" id="Start-BuildAlpine" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 1.0,3.0;"/><!--link Start to BuildLinuxFx--><path d="M962.16,34.324 C1039.32,35.978 1368.17,44.259 1408,66 C1494.86,113.411 1549.33,227.503 1567.88,271.897 " fill="none" id="Start-BuildLinuxFx" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 1.0,3.0;"/><!--link Start to BuildMac--><path d="M962.387,34.274 C991.143,35.428 1051.74,40.916 1095,66 C1120.34,80.695 1141.5,107.757 1153.7,125.819 " fill="none" id="Start-BuildMac" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 1.0,3.0;"/><!--link Start to BuildWinX64--><path d="M962.109,45.192 C969.426,50.834 977.809,58.141 984,66 C998.523,84.434 1009.76,109.139 1016.36,125.765 " fill="none" id="Start-BuildWinX64" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 1.0,3.0;"/><!--link Start to BuildWinX86--><path d="M946,58.14 C946,78.504 946,107.214 946,125.717 " fill="none" id="Start-BuildWinX86" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 1.0,3.0;"/><!--link Start to BuildWinFx--><path d="M929.752,34.828 C883.849,37.392 754.12,46.243 717,66 C690.98,79.849 669.86,107.406 657.858,125.775 " fill="none" id="Start-BuildWinFx" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 1.0,3.0;"/><!--link Start to BuildWinArm32--><path d="M929.728,45.348 C922.276,51.024 913.645,58.315 907,66 C890.971,84.536 877.122,109.221 868.69,125.813 " fill="none" id="Start-BuildWinArm32" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 1.0,3.0;"/><!--link Start to BuildWinArm64--><path d="M929.912,33.967 C902.962,34.692 848.102,39.597 812,66 C791.454,81.026 778.162,108.003 771.125,125.956 " fill="none" id="Start-BuildWinArm64" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 1.0,3.0;"/><!--link Start to BuildCG--><path d="M929.983,34.15 C862.298,34.945 602.107,39.706 578,66 C486.721,165.56 609.641,344.512 653.509,401.819 " fill="none" id="Start-BuildCG" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 1.0,3.0;"/><!--link Start to TestArtifacts--><path d="M929.91,33.95 C853.529,33.871 527.245,35.363 431,66 C400.111,75.8325 369.105,94.8815 345.58,111.6926 C333.8175,120.0982 323.9253,127.9443 316.853,133.7991 C315.969,134.531 315.129,135.2317 314.3349,135.8985 C313.9379,136.2319 313.5524,136.5569 313.1785,136.873 " fill="none" id="Start-TestArtifacts" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 1.0,3.0;"/><!--link Start to BuildMetadata--><path d="M929.862,34.136 C857.808,34.889 567.731,39.558 538,66 C437.822,155.098 456.214,342.607 464.945,401.736 " fill="none" id="Start-BuildMetadata" style="stroke: #A80036; stroke-width: 1.0; stroke-dasharray: 1.0,3.0;"/><!--link WinTest to LinuxTest--><!--link WinTest to LinuxArmTest--><!--link LinuxArmTest to LinuxArm64Test--><!-- -@startuml - -folder "Linux Builds" as LinuxBuilds { - agent "DEB" as BuildDEB - agent "RPM" as BuildRPM - agent "Alpine" as BuildAlpine - agent "Linux-FxDependent" as BuildLinuxFx - -} - -agent "macOS Build" as BuildMac - -agent "Upload build metadata" as BuildMetadata - -folder "Windows Builds" as WinBuilds { - agent "x64" as BuildWinX64 - agent "x86" as BuildWinX86 - agent "arm32" as BuildWinArm32 - agent "arm64" as BuildWinArm64 - agent "FxDependent" as BuildWinFx -} - -agent "ComponentRegistration" as BuildCG - -folder "Linux Package Scanning and Upload" as PkgScanUploadLinux { - agent "DEB" as UploadDEB - agent "RPM" as UploadRPM - agent "Alpine" as UploadAlpine - agent "Linux-FxDependent" as UploadLinuxFx -} - -folder "Package Signing and Upload" as PkgSignUpload { - agent "macOS" as SignMac - - agent "Windows" as SignWin -} - -folder "Build Test Artifacts" as TestArtifacts { - agent "Windows" as WinTest - agent "Linux" as LinuxTest - agent "Linux-ARM" as LinuxArmTest - agent "Linux-ARM64" as LinuxArm64Test -} - -agent "Compliance" as Compliance - - -agent "Create SDK and Global Tool and Upload" as BuildNuGet - - -control "Finish" as Finish -control "Start" as Start - -BuildDEB -down-> UploadDEB -BuildRPM -down-> UploadRPM -BuildLinuxFx -down-> UploadLinuxFx -BuildAlpine -down-> UploadAlpine - -BuildMac -down-> SignMac - -WinBuilds -down-> SignWin - -BuildWinX86 -down-> Compliance -BuildWinX64 -down-> Compliance -BuildWinFx -down-> Compliance - -PkgSignUpload -down-> BuildNuGet -LinuxBuilds -down-> BuildNuGet - -Compliance ~~ Finish -UploadAlpine ~~ Finish -UploadDEB ~~ Finish -UploadRPM ~~ Finish -UploadLinuxFx ~~ Finish -SignMac ~~ Finish -BuildCG ~~ Finish -BuildNuGet ~~ Finish -TestArtifacts ~~ Finish -BuildMetadata ~~ Finish - -Start ~~ BuildDEB -Start ~~ BuildRPM -Start ~~ BuildAlpine -Start ~~ BuildLinuxFx -Start ~~ BuildMac -Start ~~ BuildWinX64 -Start ~~ BuildWinX86 -Start ~~ BuildWinFx -Start ~~ BuildWinArm32 -Start ~~ BuildWinArm64 -Start ~~ BuildCG -Start ~~ TestArtifacts -Start ~~ BuildMetadata - -@enduml - -PlantUML version 1.2019.05(Sat Apr 20 09:45:36 PDT 2019) -(GPL source distribution) -Java Runtime: Java(TM) SE Runtime Environment -JVM: Java HotSpot(TM) 64-Bit Server VM -Java Version: 1.8.0_211-b12 -Operating System: Windows 10 -OS Version: 10.0 -Default Encoding: Cp1252 -Language: en -Country: US ---></g></svg> \ No newline at end of file diff --git a/tools/releaseBuild/azureDevOps/releaseBuild.yml b/tools/releaseBuild/azureDevOps/releaseBuild.yml deleted file mode 100644 index 3be90bbefbc..00000000000 --- a/tools/releaseBuild/azureDevOps/releaseBuild.yml +++ /dev/null @@ -1,379 +0,0 @@ -name: UnifiedPackageBuild-$(Build.BuildId) -trigger: - branches: - include: - - master - - release* -pr: - branches: - include: - - master - - release* - -parameters: - - name: ForceAzureBlobDelete - displayName: Delete Azure Blob - type: string - values: - - true - - false - default: false - - name: InternalSDKBlobURL - displayName: URL to the blob having internal .NET SDK - type: string - default: ' ' - -resources: - repositories: - - repository: ComplianceRepo - type: github - endpoint: ComplianceGHRepo - name: PowerShell/compliance - ref: master - -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 - # Prevents auto-injection of nuget-security-analysis@0 - - name: skipNugetSecurityAnalysis - value: true - - name: branchCounterKey - value: $[format('{0:yyyyMMdd}-{1}', pipeline.startTime,variables['Build.SourceBranch'])] - - name: branchCounter - value: $[counter(variables['branchCounterKey'], 1)] - - name: ForceAzureBlobDelete - value: ${{ parameters.ForceAzureBlobDelete }} - - name: Github_Build_Repository_Uri - value: https://github.com/powershell/powershell - - name: SBOMGenerator_Formats - value: spdx:2.2 - - name: BUILDSECMON_OPT_IN - value: true - - group: PoolNames - - name: __DOTNET_RUNTIME_FEED - value: ${{ parameters.InternalSDKBlobURL }} - -stages: - - stage: prep - jobs: - - template: templates/checkAzureContainer.yml - - - stage: macos - dependsOn: ['prep'] - jobs: - - template: templates/mac.yml - parameters: - buildArchitecture: x64 - - - template: templates/mac.yml - parameters: - buildArchitecture: arm64 - - - stage: linux - dependsOn: ['prep'] - jobs: - - template: templates/linux.yml - parameters: - buildName: deb - - - template: templates/linux.yml - parameters: - buildName: rpm - parentJob: build_deb - - - template: templates/linux.yml - parameters: - buildName: fxdependent - parentJob: build_deb - - - template: templates/linux.yml - parameters: - buildName: alpine - - - stage: windows - dependsOn: ['prep'] - jobs: - - template: templates/windows-hosted-build.yml - parameters: - Architecture: x64 - - - template: templates/windows-hosted-build.yml - parameters: - Architecture: x64 - BuildConfiguration: minSize - - - template: templates/windows-hosted-build.yml - parameters: - Architecture: x86 - - - template: templates/windows-hosted-build.yml - parameters: - Architecture: arm64 - - - template: templates/windows-hosted-build.yml - parameters: - Architecture: fxdependent - - - template: templates/windows-hosted-build.yml - parameters: - Architecture: fxdependentWinDesktop - - - stage: SignFiles - displayName: Sign files - dependsOn: ['windows', 'linux', 'macos'] - jobs: - - template: templates/mac-file-signing.yml - parameters: - buildArchitecture: x64 - - - template: templates/mac-file-signing.yml - parameters: - buildArchitecture: arm64 - - - job: SignFilesWinLinux - pool: - name: $(windowsPool) - demands: - - ImageOverride -equals PSMMS2019-Secure - displayName: Sign files - - variables: - - group: ESRP - - name: runCodesignValidationInjection - value: false - - name: NugetSecurityAnalysisWarningLevel - value: none - - name: repoFolder - value: PowerShell - - name: repoRoot - value: $(Agent.BuildDirectory)\$(repoFolder) - - name: complianceRepoFolder - value: compliance - - strategy: - matrix: - linux-x64: - runtime: linux-x64 - unsignedBuildArtifactContainer: pwshLinuxBuild.tar.gz - unsignedBuildArtifactName: pwshLinuxBuild.tar.gz - signedBuildArtifactName: pwshLinuxBuild.tar.gz - signedArtifactContainer: authenticode-signed - linux-x64-Alpine: - runtime: linux-x64-Alpine - unsignedBuildArtifactContainer: pwshLinuxBuildAlpine.tar.gz - unsignedBuildArtifactName: pwshLinuxBuild.tar.gz - signedBuildArtifactName: pwshLinuxBuildAlpine.tar.gz - signedArtifactContainer: authenticode-signed - linux-x64-Alpine-Fxdependent: - runtime: linux-x64-Alpine-Fxdependent - unsignedBuildArtifactContainer: pwshAlpineFxdBuildAmd64.tar.gz - unsignedBuildArtifactName: pwshAlpineFxdBuildAmd64.tar.gz - signedBuildArtifactName: pwshAlpineFxdBuildAmd64.tar.gz - signedArtifactContainer: authenticode-signed - linux-arm32: - runtime: linux-arm32 - unsignedBuildArtifactContainer: pwshLinuxBuildArm32.tar.gz - unsignedBuildArtifactName: pwshLinuxBuildArm32.tar.gz - signedBuildArtifactName: pwshLinuxBuildArm32.tar.gz - signedArtifactContainer: authenticode-signed - linux-arm64: - runtime: linux-arm64 - unsignedBuildArtifactContainer: pwshLinuxBuildArm64.tar.gz - unsignedBuildArtifactName: pwshLinuxBuildArm64.tar.gz - signedBuildArtifactName: pwshLinuxBuildArm64.tar.gz - signedArtifactContainer: authenticode-signed - linux-fxd: - runtime: linux-fxd - unsignedBuildArtifactContainer: pwshLinuxBuildFxdependent.tar.gz - unsignedBuildArtifactName: pwshLinuxBuild.tar.gz - signedBuildArtifactName: pwshLinuxBuildFxdependent.tar.gz - signedArtifactContainer: authenticode-signed - linux-mariner: - runtime: linux-mariner - unsignedBuildArtifactContainer: pwshMarinerBuildAmd64.tar.gz - unsignedBuildArtifactName: pwshMarinerBuildAmd64.tar.gz - signedBuildArtifactName: pwshMarinerBuildAmd64.tar.gz - signedArtifactContainer: authenticode-signed - linux-arm64-mariner: - runtime: linux-arm64-mariner - unsignedBuildArtifactContainer: pwshMarinerBuildArm64.tar.gz - unsignedBuildArtifactName: pwshMarinerBuildArm64.tar.gz - signedBuildArtifactName: pwshMarinerBuildArm64.tar.gz - signedArtifactContainer: authenticode-signed - linux-minsize: - runtime: linux-minsize - unsignedBuildArtifactContainer: pwshLinuxBuildMinSize.tar.gz - unsignedBuildArtifactName: pwshLinuxBuildMinSize.tar.gz - signedBuildArtifactName: pwshLinuxBuildMinSize.tar.gz - signedArtifactContainer: authenticode-signed - win-x64: - runtime: win-x64 - unsignedBuildArtifactContainer: results - unsignedBuildArtifactName: '**/*-symbols-win-x64.zip' - signedBuildArtifactName: '-symbols-win-x64-signed.zip' - signedArtifactContainer: results - win-x86: - runtime: win-x86 - unsignedBuildArtifactContainer: results - unsignedBuildArtifactName: '**/*-symbols-win-x86.zip' - signedBuildArtifactName: '-symbols-win-x86-signed.zip' - signedArtifactContainer: results - win-arm64: - runtime: win-arm64 - unsignedBuildArtifactContainer: results - unsignedBuildArtifactName: '**/*-symbols-win-arm64.zip' - signedBuildArtifactName: '-symbols-win-arm64-signed.zip' - signedArtifactContainer: results - win-x64-gc: - runtime: win-x64-gc - unsignedBuildArtifactContainer: results - unsignedBuildArtifactName: '**/*-symbols-win-x64-gc.zip' - signedBuildArtifactName: '-symbols-win-x64-gc-signed.zip' - signedArtifactContainer: results - win-fxdependent: - runtime: win-fxdependent - unsignedBuildArtifactContainer: results - unsignedBuildArtifactName: '**/*-symbols-win-fxdependent.zip' - signedBuildArtifactName: '-symbols-win-fxdependent-signed.zip' - signedArtifactContainer: results - win-fxdependentWinDesktop: - runtime: win-fxdependentWinDesktop - unsignedBuildArtifactContainer: results - unsignedBuildArtifactName: '**/*-symbols-win-fxdependentWinDesktop.zip' - signedBuildArtifactName: '-symbols-win-fxdependentWinDesktop-signed.zip' - signedArtifactContainer: results - steps: - - template: templates/sign-build-file.yml - - - stage: mac_packaging - displayName: macOS packaging - dependsOn: ['SignFiles'] - jobs: - - template: templates/mac-package-build.yml - parameters: - buildArchitecture: x64 - - - template: templates/mac-package-build.yml - parameters: - buildArchitecture: arm64 - - - stage: linux_packaging - displayName: Linux Packaging - dependsOn: ['SignFiles'] - jobs: - - template: templates/linux-packaging.yml - parameters: - buildName: deb - - - template: templates/linux-packaging.yml - parameters: - buildName: rpm - uploadDisplayName: Upload and Sign - - - template: templates/linux-packaging.yml - parameters: - buildName: alpine - - - template: templates/linux-packaging.yml - parameters: - buildName: fxdependent - - - stage: win_packaging - displayName: Windows Packaging - dependsOn: ['SignFiles'] - jobs: - - template: templates/windows-packaging.yml - parameters: - Architecture: x64 - parentJob: build_windows_x64_release - - - template: templates/windows-packaging.yml - parameters: - Architecture: x64 - BuildConfiguration: minSize - parentJob: build_windows_x64_minSize - - - template: templates/windows-packaging.yml - parameters: - Architecture: x86 - parentJob: build_windows_x86_release - - - template: templates/windows-packaging.yml - parameters: - Architecture: arm64 - parentJob: build_windows_arm64_release - - - template: templates/windows-packaging.yml - parameters: - Architecture: fxdependent - parentJob: build_windows_fxdependent_release - - - template: templates/windows-packaging.yml - parameters: - Architecture: fxdependentWinDesktop - parentJob: build_windows_fxdependentWinDesktop_release - - - stage: package_signing - displayName: Package Signing - dependsOn: ['mac_packaging', 'linux_packaging', 'win_packaging'] - jobs: - - template: templates/windows-package-signing.yml - - - template: templates/mac-package-signing.yml - parameters: - buildArchitecture: x64 - - - template: templates/mac-package-signing.yml - parameters: - buildArchitecture: arm64 - - - stage: nuget_and_json - displayName: NuGet Packaging and Build Json - dependsOn: ['package_signing'] - jobs: - - template: templates/nuget.yml - - template: templates/json.yml - - # This is done late so that we dont use resources before the big signing and packaging tasks. - - stage: compliance - dependsOn: ['package_signing'] - jobs: - - template: templates/compliance.yml - - - stage: test_and_release_artifacts - displayName: Test and Release Artifacts - dependsOn: ['prep'] - jobs: - - template: templates/testartifacts.yml - - - job: release_json - displayName: Create and Upload release.json - pool: - name: $(windowsPool) - demands: - - ImageOverride -equals PSMMS2019-Secure - steps: - - checkout: self - clean: true - - template: templates/SetVersionVariables.yml - parameters: - ReleaseTagVar: $(ReleaseTagVar) - - - powershell: | - $metadata = Get-Content '$(Build.SourcesDirectory)/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" - Write-Host "##vso[artifact.upload containerfolder=metadata;artifactname=metadata]$(Build.StagingDirectory)\release.json" - displayName: Create and upload release.json file to build artifact - retryCountOnTaskFailure: 2 - - - template: /tools/releaseBuild/azureDevOps/templates/step/finalize.yml diff --git a/tools/releaseBuild/azureDevOps/releasePipeline.yml b/tools/releaseBuild/azureDevOps/releasePipeline.yml deleted file mode 100644 index e21f6d590fe..00000000000 --- a/tools/releaseBuild/azureDevOps/releasePipeline.yml +++ /dev/null @@ -1,673 +0,0 @@ -trigger: none - -# needed to disable CI trigger and allow manual trigger -# when the branch is same as pipeline source, the latest build from the source is used. -# all environment used are for manual tasks and approvals. - -parameters: - - name: skipPackagesMsftComPublish - displayName: Skip actual publishing to Packages.microsoft.com, AFTER we upload it. Used to test the publishing script. - default: false - type: boolean - - name: skipNugetPublish - displayName: Skip nuget publishing. Used in testing publishing stage. - default: false - type: boolean - -resources: - pipelines: - - pipeline: releasePipeline - source: 'Coordinated Packages' - trigger: - branches: - - release/* - - repositories: - - repository: Internal-PowerShellTeam-Tools - type: git - trigger: none - name: Internal-PowerShellTeam-Tools - ref: main-mirror - - - repository: ComplianceRepo - type: github - endpoint: ComplianceGHRepo - name: PowerShell/compliance - ref: master - -variables: - - name: runCodesignValidationInjection - value : false - - name: nugetMultiFeedWarnLevel - value: none - - name: NugetSecurityAnalysisWarningLevel - value: none - - name: skipComponentGovernanceDetection - value: true - - name: BUILDSECMON_OPT_IN - value: true - - group: ReleasePipelineSecrets - - group: PipelineExecutionPats - -stages: -- stage: MSIXBundle - displayName: Create MSIX Bundle package - dependsOn: [] - jobs: - - template: templates/release-MsixBundle.yml - -- stage: ValidateSDK - displayName: Validate SDK - dependsOn: [] - jobs: - - template: templates/release-SDKTests.yml - parameters: - jobName: WinSDK - displayName: Windows SDK Test - imageName: windows-latest - - - template: templates/release-SDKTests.yml - parameters: - jobName: LinuxSDK - displayName: Linux SDK Test - imageName: ubuntu-latest - - - template: templates/release-SDKTests.yml - parameters: - jobName: macOSSDK - displayName: macOS SDK Test - imageName: macOS-latest - -- stage: PRCreation - displayName: Create PR in GH Master - dependsOn: [] - jobs: - - deployment: CreatePRInMaster - displayName: Update README.md and metadata.json - pool: server - environment: PSReleaseCreatePR - -- stage: ValidateGlobalTool - displayName: Validate Global Tool - dependsOn: [] - jobs: - - template: templates/release-GlobalToolTest.yml - parameters: - jobName: WinGblTool - displayName: Global Tool Test Windows - imageName: windows-latest - globalToolExeName: 'pwsh.exe' - globalToolPackageName: 'PowerShell.Windows.x64' - - - template: templates/release-GlobalToolTest.yml - parameters: - jobName: LinuxWinGblTool - displayName: Global Tool Test Linux - imageName: ubuntu-latest - globalToolExeName: 'pwsh' - globalToolPackageName: 'PowerShell.Linux.x64' - -- stage: ValidateFxdPackage - displayName: Validate Fxd Package - dependsOn: [] - jobs: - - template: templates/release-ValidateFxdPackage.yml - parameters: - jobName: WinFxdPackage - displayName: Fxd Package Test Win - imageName: windows-latest - packageNamePattern: '**/*win-fxdependent.zip' - - - template: templates/release-ValidateFxdPackage.yml - parameters: - jobName: FxdPackageWindDesktop - displayName: Fxd Package Test WinDesktop - imageName: windows-latest - packageNamePattern: '**/*win-fxdependentWinDesktop.zip' - - - template: templates/release-ValidateFxdPackage.yml - parameters: - jobName: FxdPackageLinux - displayName: Fxd Package Test Linux - imageName: ubuntu-latest - packageNamePattern: '**/*linux-x64-fxdependent.tar.gz' - - - template: templates/release-ValidateFxdPackage.yml - parameters: - jobName: FxdPackageLinuxonARM - displayName: Fxd Package Test Linux ARM64 - imageName: 'PSMMSUbuntu20.04-ARM64-secure' - packageNamePattern: '**/*linux-x64-fxdependent.tar.gz' - use1ES: true - -- stage: StaticPkgValidation - dependsOn: [] - displayName: Static package validation - jobs: - - job: ValidatePkgNames - displayName: Validate Package Names - pool: - name: PowerShell1ES - demands: - - ImageOverride -equals PSMMS2019-Secure - variables: - - group: 'Azure Blob variable group' - steps: - - template: templates/release-ValidatePackageNames.yml - - job: ValidatePkgBOM - displayName: Validate Package BOM - pool: - # testing - vmImage: ubuntu-latest - steps: - - template: templates/release-ValidatePackageBOM.yml - -- stage: StartDocker - dependsOn: [] - displayName: Kick Off Docker Staging build - jobs: - - deployment: PSDockerKickOff - displayName: Start Docker build - pool: server - environment: PSReleaseDockerKickOff - -- stage: ManualValidation - dependsOn: [] - displayName: Manual Validation - jobs: - - template: templates/release/approvalJob.yml - parameters: - displayName: Validate Windows Packages - jobName: ValidateWinPkg - instructions: | - Validate zip and msipackages on Windows Server 2012 R2 - - - template: templates/release/approvalJob.yml - parameters: - displayName: Validate OSX Packages - jobName: ValidateOsxPkg - instructions: | - Validate tar.gz package on osx-arm64 - -- stage: ReleaseAutomation - displayName: Release Automation - dependsOn: [] - jobs: - - job: KickOffRA - displayName: Kickoff Release Automation - timeoutInMinutes: 240 - - pool: - name: PowerShell1ES - demands: - - ImageOverride -equals PSMMS2019-Secure - - steps: - - checkout: Internal-PowerShellTeam-Tools - - task: DownloadPipelineArtifact@2 - inputs: - source: specific - project: PowerShellCore - pipeline: '696' - preferTriggeringPipeline: true - runVersion: latestFromBranch - runBranch: '$(Build.SourceBranch)' - artifact: metadata - path: '$(Pipeline.Workspace)/releasePipeline/metadata' - - - pwsh: | - Get-ChildItem -Path $(Build.SourcesDirectory) - Import-Module $(Build.SourcesDirectory)\ReleaseTools\AzDO -Force - Set-AzDoProjectInfo -ProjectOwner PowerShell-Rel -ProjectName Release-Automation - Set-AzDoAuthToken -Token $(powershellRelExecutionPat) - $packageBuildID = $(resources.pipeline.releasePipeline.runID) - $metadata = Get-Content -Raw -Path '$(Pipeline.Workspace)/releasePipeline/metadata/release.json' | ConvertFrom-Json - $buildInvocationInfo = Start-AzDOBuild -BuildDefinitionId 10 -BuildArguments @{ POWERSHELL_PACKAGE_BUILD_BUILDID = $packageBuildID } -Tag $metadata.ReleaseVersion, 'InProgress' -PassThru - Write-Verbose -Verbose "Kicked off release automation:`n$($buildInvocationInfo | Out-String)" - $status = $buildInvocationInfo | Wait-AzDOBuildStatus -Status Completed -timeoutMinutes 240 - if ($status.result -ne 'Succeeded') { - Write-Verbose "There are errors in release automation tests. Please triage failures." - } - - - template: templates/release/approvalJob.yml - parameters: - displayName: Triage Release Automation Results - jobName: TriageRA - dependsOnJob: KickOffRA - instructions: | - Validate all the test failures and continue when signed off - - - job: MarkRASignOff - displayName: Mark release automation signoff - dependsOn: TriageRA - - pool: - name: PowerShell1ES - demands: - - ImageOverride -equals PSMMS2019-Secure - - steps: - - checkout: Internal-PowerShellTeam-Tools - - task: DownloadPipelineArtifact@2 - inputs: - source: specific - project: PowerShellCore - pipeline: '696' - preferTriggeringPipeline: true - runVersion: latestFromBranch - runBranch: '$(Build.SourceBranch)' - artifact: metadata - path: '$(Pipeline.Workspace)/releasePipeline/metadata' - - - pwsh: | - Import-Module $(Build.SourcesDirectory)\ReleaseTools\AzDO -Force - Set-AzDoProjectInfo -ProjectOwner PowerShell-Rel -ProjectName Release-Automation - Set-AzDoAuthToken -Token $(powershellRelExecutionPat) - $metadata = Get-Content -Raw -Path '$(Pipeline.Workspace)/releasePipeline/metadata/release.json' | ConvertFrom-Json - $azDOBuild = Get-AzDOBuild -buildDefinitionId 10 -MaximumResult 100 | Where-Object { $_.tags -in $metadata.ReleaseVersion } - $azDoBuild | Remove-AzDOBuildTag -tag 'InProgress' -Pass | Add-AzDOBuildTag -tag 'SignedOff' - displayName: Signoff Release-Automation run - -- stage: UpdateChangeLog - displayName: Update the changelog - # do not include stages that are likely to fail in dependency as there is no way to force deploy. - dependsOn: - - MSIXBundle - - ValidateSDK - - PRCreation - - StaticPkgValidation - - StartDocker - - ManualValidation - - ValidateFxdPackage - - ValidateGlobalTool - - jobs: - - template: templates/release/approvalJob.yml - 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: BlobPublic - displayName: Make Blob Public - # do not include stages that are likely to fail in dependency as there is no way to force deploy. - dependsOn: UpdateChangeLog - - # The environment here is used for approval. - jobs: - - deployment: AzureBlobPublic - displayName: Make Azure Blob Public - - pool: - name: PowerShell1ES - demands: - - ImageOverride -equals PSMMS2019-Secure - - variables: - - group: 'Staging_ACR' - environment: PSReleaseAzureBlobPublic - strategy: - runOnce: - deploy: - steps: - - template: templates/release-MakeContainerPublic.yml - - - template: templates/release/approvalJob.yml - parameters: - displayName: Copy Global tool packages to PSInfra storage - jobName: CopyBlobApproval - instructions: | - Approval for Copy global tool packages to PSInfra storage - - - job: PSInfraBlobPublic - displayName: Copy global tools to PSInfra storage - dependsOn: CopyBlobApproval - - pool: - name: PowerShell1ES - demands: - - ImageOverride -equals PSMMS2019-Secure - - variables: - - group: 'PSInfraStorage' - - steps: - - template: templates/release-CopyGlobalTools.yml - parameters: - sourceContainerName: 'tool-private' - destinationContainerName: 'tool' - sourceStorageAccountName: '$(GlobalToolStorageAccount)' - destinationStorageAccountName: '$(PSInfraStorageAccount)' - blobPrefix: '$(Version)' - -- stage: GitHubTasks - displayName: GitHub tasks - dependsOn: BlobPublic - jobs: - - job: GitHubDraft - displayName: Create GitHub Draft release - - pool: - name: PowerShell1ES - demands: - - ImageOverride -equals PSMMS2019-Secure - - variables: - - group: 'Azure Blob variable group' - - group: mscodehub-feed-read-general - - group: mscodehub-feed-read-akv - - group: ReleasePipelineSecrets - steps: - - template: templates/release-CreateGitHubDraft.yml - - - deployment: PushTag - dependsOn: GitHubDraft - displayName: Push Git Tag - pool : server - environment: PSReleasePushTag - - - deployment: MakeDraftPublic - dependsOn: PushTag - displayName: Make GitHub Draft public - pool : server - environment: PSReleaseDraftPublic - -- stage: PublishPackages - displayName: Publish packages - dependsOn: GitHubTasks - jobs: - - job: PublishNuget - - pool: - name: PowerShell1ES - demands: - - ImageOverride -equals PSMMS2019-Secure - - steps: - - template: templates/release-ReleaseToNuGet.yml - parameters: - skipPublish: ${{ parameters.skipNugetPublish }} - - - job: PublishPkgsMsftCom - - timeoutInMinutes: 120 - pool: - name: PowerShell1ES - demands: - - ImageOverride -equals PSMMSUbuntu20.04-Secure - - variables: - - group: mscodehub-feed-read-general - - group: mscodehub-feed-read-akv - - group: 'packages.microsoft.com' - - group: 'mscodehub-code-read-akv' - steps: - - template: templates/release-PublishPackageMsftCom.yml - parameters: - skipPublish: ${{ parameters.skipPackagesMsftComPublish }} - -- stage: PublishSymbols - displayName: Publish symbols - dependsOn: PublishPackages - jobs: - - job: PublishSymbol - - pool: - name: PowerShell1ES - demands: - - ImageOverride -equals PSMMS2019-Secure - - steps: - - template: templates/release-PublishSymbols.yml - -- stage: ChangesToMaster - displayName: Ensure changes are in GH master - dependsOn: PublishPackages - jobs: - - template: templates/release/approvalJob.yml - 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: ReleaseDocker - displayName: Release Docker - dependsOn: - - GitHubTasks - jobs: - - deployment: ReleaseDocker - displayName: Release Docker - pool: server - environment: PSReleaseDockerRelease - -- stage: ReleaseSnap - displayName: Release Snap - dependsOn: - - PublishPackages - - ChangesToMaster - variables: - # adds newPwshOrgName (exists in new and old org) - - group: PowerShellRelease - jobs: - - job: KickoffSnap - displayName: Kickoff Snap build - - pool: - name: PowerShell1ES - demands: - - ImageOverride -equals PSMMS2019-Secure - - steps: - - checkout: Internal-PowerShellTeam-Tools - - task: DownloadPipelineArtifact@2 - inputs: - source: specific - project: PowerShellCore - pipeline: '696' - preferTriggeringPipeline: true - runVersion: latestFromBranch - runBranch: '$(Build.SourceBranch)' - artifact: metadata - path: '$(Pipeline.Workspace)/releasePipeline/metadata' - - pwsh: | - Import-Module $(Build.SourcesDirectory)\ReleaseTools\AzDO -Force - Set-AzDoProjectInfo -ProjectOwner PowerShell-Rel -ProjectName PowerShell - Set-AzDoAuthToken -Token $(powershellRelExecutionPat) - $metadata = Get-Content -Raw -Path '$(Pipeline.Workspace)/releasePipeline/metadata/release.json' | ConvertFrom-Json - $buildInvocationInfo = Start-AzDOBuild -BuildDefinitionId 49 -Tag $metadata.ReleaseVersion, 'InProgress' -PassThru - Write-Verbose -Verbose "Kicked off snap build: $($buildInvocationInfo.WebUrl)" - $status = $buildInvocationInfo | Wait-AzDOBuildStatus -Status Completed -timeoutMinutes 60 - if ($status.result -ne 'Succeeded') { - throw "There are errors in snap build!!" - } - - - template: templates/release/approvalJob.yml - parameters: - displayName: Approve the release - jobName: SnapEnd - dependsOnJob: KickoffSnap - instructions: | - Once the build is finished, approve the release of all channels. - - - job: MarkSnapSignOff - displayName: Mark release automation signoff - dependsOn: SnapEnd - - pool: - name: PowerShell1ES - demands: - - ImageOverride -equals PSMMS2019-Secure - - steps: - - checkout: Internal-PowerShellTeam-Tools - - task: DownloadPipelineArtifact@2 - inputs: - source: specific - project: PowerShellCore - pipeline: '696' - preferTriggeringPipeline: true - runVersion: latestFromBranch - runBranch: '$(Build.SourceBranch)' - artifact: metadata - path: '$(Pipeline.Workspace)/releasePipeline/metadata' - - pwsh: | - Import-Module $(Build.SourcesDirectory)\ReleaseTools\AzDO -Force - Set-AzDoProjectInfo -ProjectOwner PowerShell-Rel -ProjectName PowerShell - Set-AzDoAuthToken -Token $(powershellRelExecutionPat) - $metadata = Get-Content -Raw -Path '$(Pipeline.Workspace)/releasePipeline/metadata/release.json' | ConvertFrom-Json - $azDOBuild = Get-AzDOBuild -buildDefinitionId 49 -MaximumResult 100 | Where-Object { $_.tags -in $metadata.ReleaseVersion } - $azDoBuild | Remove-AzDOBuildTag -tag 'InProgress' -Pass | Add-AzDOBuildTag -tag 'SignedOff' - displayName: Signoff Release-Automation run - -- stage: ReleaseToMU - displayName: Release to MU - dependsOn: - - PublishPackages - - ChangesToMaster - jobs: - - template: templates/release/approvalJob.yml - parameters: - displayName: Release to MU - instructions: | - Notify the PM team to start the process of releasing to MU. - -- stage: UpdateDotnetDocker - dependsOn: GitHubTasks - displayName: Update DotNet SDK Docker images - jobs: - - template: templates/release/approvalJob.yml - 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\ -- <dotnetversion> --product-version powershell=<powershellversion> --compute-shas - 4. create PR targeting nightly branch - -- stage: UpdateWinGet - dependsOn: GitHubTasks - displayName: Add manifest entry to winget - jobs: - - template: templates/release/approvalJob.yml - 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: GitHubTasks - displayName: Publish MSIX to store - jobs: - - template: templates/release/approvalJob.yml - parameters: - displayName: Publish the MSIX Bundle package to store - jobName: PublishMsix - instructions: | - Ask Steve to release MSIX bundle package to Store - -- stage: BuildInfoJson - dependsOn: GitHubTasks - displayName: Upload BuildInfoJson - jobs: - - deployment: UploadJson - displayName: Upload BuildInfoJson - - pool: - name: PowerShell1ES - demands: - - ImageOverride -equals PSMMS2019-Secure - - variables: - - group: 'Azure Blob variable group' - environment: PSReleaseBuildInfoJson - strategy: - runOnce: - deploy: - steps: - - template: templates/release-BuildJson.yml - -- stage: ReleaseVPack - dependsOn: GitHubTasks - displayName: Release VPack - jobs: - - job: KickoffvPack - displayName: Kickoff vPack build - - pool: - name: PowerShell1ES - demands: - - ImageOverride -equals PSMMS2019-Secure - - steps: - - checkout: Internal-PowerShellTeam-Tools - - task: DownloadPipelineArtifact@2 - inputs: - source: specific - project: PowerShellCore - pipeline: '696' - preferTriggeringPipeline: true - runVersion: latestFromBranch - runBranch: '$(Build.SourceBranch)' - artifact: metadata - path: '$(Pipeline.Workspace)/releasePipeline/metadata' - - - pwsh: | - Import-Module $(Build.SourcesDirectory)\ReleaseTools\AzDO -Force - Set-AzDoProjectInfo -ProjectOwner mscodehub -ProjectName PowerShellCore - Set-AzDoAuthToken -Token $(mscodehubBuildExecutionPat) - $metadata = Get-Content -Raw -Path '$(Pipeline.Workspace)/releasePipeline/metadata/release.json' | ConvertFrom-Json - $releaseVersion = $metadata.ReleaseVersion -replace '^v','' - $semanticVersion = [System.Management.Automation.SemanticVersion]$releaseVersion - $isPreview = $semanticVersion.PreReleaseLabel -ne $null - - if (-not $isPreview) { - $buildInvocationInfo = Start-AzDOBuild -BuildDefinitionId 1238 -Branch '$(Build.SourceBranch)' -Tag $metadata.ReleaseVersion, 'InProgress' -PassThru - Write-Verbose -Verbose "Kicked off vPack build: $($buildInvocationInfo.WebUrl)" - $status = $buildInvocationInfo | Wait-AzDOBuildStatus -Status Completed -timeoutMinutes 60 - if ($status.result -ne 'Succeeded') { - throw "There are errors in snap build!!" - } - else { - $buildInvocationInfo | Remove-AzDOBuildTag -tag 'InProgress' -Pass | Add-AzDOBuildTag -tag 'SignedOff' - } - } - else { - Write-Verbose -Verbose "This is a preview release with version: $semanticVersion skipping releasing vPack" - } - -- stage: ReleaseDeps - dependsOn: GitHubTasks - displayName: Update pwsh.deps.json links - jobs: - - template: templates/release-UpdateDepsJson.yml - -- stage: ReleaseClose - displayName: Finish Release - dependsOn: - - ReleaseVPack - - BuildInfoJson - - UpdateDotnetDocker - - ReleaseDocker - - ReleaseSnap - - ChangesToMaster - - ReleaseDeps - jobs: - - template: templates/release/approvalJob.yml - parameters: - displayName: Retain Build - jobName: RetainBuild - instructions: | - Retain the build - - - template: templates/release/approvalJob.yml - parameters: - displayName: Delete release branch - jobName: DeleteBranch - instructions: | - Delete release diff --git a/tools/releaseBuild/azureDevOps/templates/SetVersionVariables.yml b/tools/releaseBuild/azureDevOps/templates/SetVersionVariables.yml deleted file mode 100644 index da1889f5bf7..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/SetVersionVariables.yml +++ /dev/null @@ -1,63 +0,0 @@ -parameters: - ReleaseTagVar: v6.2.0 - ReleaseTagVarName: ReleaseTagVar - CreateJson: 'no' - UseJson: 'yes' - -steps: -- ${{ if eq(parameters['UseJson'],'yes') }}: - - task: DownloadBuildArtifacts@0 - inputs: - artifactName: 'BuildInfoJson' - itemPattern: '**/*.json' - downloadPath: '$(System.ArtifactsDirectory)' - displayName: Download Build Info Json - -- 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' - -- powershell: | - $createJson = ("${{ parameters.CreateJson }}" -ne "no") - $releaseTag = & "$env:REPOROOT/tools/releaseBuild/setReleaseTag.ps1" -ReleaseTag ${{ parameters.ReleaseTagVar }} -Variable "${{ parameters.ReleaseTagVarName }}" -CreateJson:$createJson - $version = $releaseTag.Substring(1) - $vstsCommandString = "vso[task.setvariable variable=Version]$version" - Write-Host ("sending " + $vstsCommandString) - Write-Host "##$vstsCommandString" - - $azureVersion = $releaseTag.ToLowerInvariant() -replace '\.', '-' - $vstsCommandString = "vso[task.setvariable variable=AzureVersion]$azureVersion" - Write-Host ("sending " + $vstsCommandString) - Write-Host "##$vstsCommandString" - displayName: 'Set ${{ parameters.ReleaseTagVarName }} and other version Variables' - -- powershell: | - Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose - displayName: Capture environment - condition: succeededOrFailed() diff --git a/tools/releaseBuild/azureDevOps/templates/checkAzureContainer.yml b/tools/releaseBuild/azureDevOps/templates/checkAzureContainer.yml deleted file mode 100644 index af6451004e4..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/checkAzureContainer.yml +++ /dev/null @@ -1,51 +0,0 @@ -jobs: -- job: DeleteBlob - variables: - - name: runCodesignValidationInjection - value : false - - name: NugetSecurityAnalysisWarningLevel - value: none - - group: Azure Blob variable group - displayName: Delete blob is exists - pool: - name: PowerShell1ES - demands: - - ImageOverride -equals PSMMS2019-Secure - steps: - - checkout: self - clean: true - - - template: SetVersionVariables.yml - parameters: - ReleaseTagVar: $(ReleaseTagVar) - CreateJson: yes - UseJson: no - - - task: AzurePowerShell@4 - displayName: Check if blob exists and delete if specified - inputs: - azureSubscription: '$(AzureFileCopySubscription)' - scriptType: inlineScript - azurePowerShellVersion: latestVersion - inline: | - try { - $container = Get-AzStorageContainer -Container '$(AzureVersion)' -Context (New-AzStorageContext -StorageAccountName '$(StorageAccount)') -ErrorAction Stop - - if ($container -ne $null -and '$(ForceAzureBlobDelete)' -eq 'false') { - throw 'Azure blob container $(AzureVersion) already exists. To overwrite, use ForceAzureBlobDelete parameter' - } - elseif ($container -ne $null -and '$(ForceAzureBlobDelete)' -eq 'true') { - Write-Verbose -Verbose 'Removing container $(AzureVersion) due to ForceAzureBlobDelete parameter' - Remove-AzStorageContainer -Name '$(AzureVersion)' -Context (New-AzStorageContext -StorageAccountName '$(StorageAccount)') -Force - } - } - catch { - if ($_.FullyQualifiedErrorId -eq 'ResourceNotFoundException,Microsoft.WindowsAzure.Commands.Storage.Blob.Cmdlet.GetAzureStorageContainerCommand') { - Write-Verbose -Verbose 'Container "$(AzureVersion)" does not exists.' - } - else { - throw $_ - } - } - - - template: /tools/releaseBuild/azureDevOps/templates/step/finalize.yml diff --git a/tools/releaseBuild/azureDevOps/templates/cloneToOfficialPath.yml b/tools/releaseBuild/azureDevOps/templates/cloneToOfficialPath.yml deleted file mode 100644 index 352458390f9..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/cloneToOfficialPath.yml +++ /dev/null @@ -1,19 +0,0 @@ -parameters: - nativePathRoot: '' - -steps: - - powershell: | - $dirSeparatorChar = [system.io.path]::DirectorySeparatorChar - $nativePath = "${{parameters.nativePathRoot }}${dirSeparatorChar}PowerShell" - Write-Host "##vso[task.setvariable variable=PowerShellRoot]$nativePath" - - if ((Test-Path "$nativePath")) { - Remove-Item -Path "$nativePath" -Force -Recurse -Verbose -ErrorAction ignore - } - else { - Write-Verbose -Verbose -Message "No cleanup required." - } - - git clone --quiet $env:REPOROOT $nativePath - displayName: Clone PowerShell Repo to /PowerShell - errorActionPreference: silentlycontinue diff --git a/tools/releaseBuild/azureDevOps/templates/compliance.yml b/tools/releaseBuild/azureDevOps/templates/compliance.yml deleted file mode 100644 index 0a416389bf4..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/compliance.yml +++ /dev/null @@ -1,124 +0,0 @@ -parameters: - parentJobs: [] - -jobs: -- job: compliance - variables: - - name: runCodesignValidationInjection - value : false - - name: NugetSecurityAnalysisWarningLevel - value: none - - displayName: Compliance - dependsOn: - ${{ parameters.parentJobs }} - pool: - name: PowerShell1ES - demands: - - ImageOverride -equals PSMMS2019-Secure - - steps: - - checkout: self - clean: true - - - template: SetVersionVariables.yml - parameters: - ReleaseTagVar: $(ReleaseTagVar) - - - task: DownloadBuildArtifacts@0 - displayName: 'Download artifacts' - inputs: - buildType: current - downloadType: single - artifactName: results - downloadPath: '$(System.ArtifactsDirectory)' - - - powershell: | - dir "$(System.ArtifactsDirectory)\*" -Recurse - displayName: 'Capture artifacts directory' - continueOnError: true - - - template: expand-compliance.yml - parameters: - architecture: fxdependent - version: $(version) - - - template: expand-compliance.yml - parameters: - architecture: x86 - version: $(version) - - - template: expand-compliance.yml - parameters: - architecture: x64 - version: $(version) - - - task: securedevelopmentteam.vss-secure-development-tools.build-task-antimalware.AntiMalware@3 - displayName: 'Run Defender Scan' - - - task: securedevelopmentteam.vss-secure-development-tools.build-task-binskim.BinSkim@3 - displayName: 'Run BinSkim ' - inputs: - InputType: Basic - AnalyzeTarget: '$(CompliancePath)\*.dll;$(CompliancePath)\*.exe' - AnalyzeSymPath: 'SRV*' - AnalyzeVerbose: true - AnalyzeHashes: true - AnalyzeStatistics: true - continueOnError: true - - # add RoslynAnalyzers - - - task: securedevelopmentteam.vss-secure-development-tools.build-task-autoapplicability.AutoApplicability@1 - displayName: 'Run AutoApplicability' - inputs: - ExternalRelease: true - IsSoftware: true - DataSensitivity: lbi - continueOnError: true - - # add codeMetrics - - - task: securedevelopmentteam.vss-secure-development-tools.build-task-vulnerabilityassessment.VulnerabilityAssessment@0 - displayName: 'Run Vulnerability Assessment' - continueOnError: true - - # FXCop is not applicable - - # PreFASt is not applicable - - - task: securedevelopmentteam.vss-secure-development-tools.build-task-publishsecurityanalysislogs.PublishSecurityAnalysisLogs@2 - displayName: 'Publish Security Analysis Logs to Build Artifacts' - continueOnError: true - - - task: securedevelopmentteam.vss-secure-development-tools.build-task-uploadtotsa.TSAUpload@1 - displayName: 'TSA upload to Codebase: PowerShellCore_201906' - inputs: - tsaVersion: TsaV2 - codeBaseName: 'PowerShellCore_201906' - uploadAPIScan: false - uploadBinSkim: true - uploadCredScan: false - uploadFortifySCA: false - uploadFxCop: false - uploadModernCop: false - uploadPoliCheck: false - uploadPREfast: false - uploadRoslyn: false - uploadTSLint: false - - - task: securedevelopmentteam.vss-secure-development-tools.build-task-report.SdtReport@1 - displayName: 'Create Security Analysis Report' - inputs: - TsvFile: false - APIScan: false - BinSkim: true - CredScan: true - PoliCheck: true - PoliCheckBreakOn: Severity2Above - - - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 - displayName: 'Component Detection' - inputs: - sourceScanPath: '$(Build.SourcesDirectory)\tools' - snapshotForceEnabled: true diff --git a/tools/releaseBuild/azureDevOps/templates/compliance/apiscan.yml b/tools/releaseBuild/azureDevOps/templates/compliance/apiscan.yml deleted file mode 100644 index 2047ad3e0b9..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/compliance/apiscan.yml +++ /dev/null @@ -1,180 +0,0 @@ -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 - - pool: - name: PowerShell1ES - demands: - - ImageOverride -equals PSMMS2019-Secure - - # APIScan can take a long time - timeoutInMinutes: 180 - - steps: - - template: ../SetVersionVariables.yml - parameters: - ReleaseTagVar: $(ReleaseTagVar) - CreateJson: yes - UseJson: no - - - template: ../insert-nuget-config-azfeed.yml - parameters: - repoRoot: '$(Build.SourcesDirectory)' - - - pwsh: | - Import-Module .\build.psm1 -force - Start-PSBootstrap - workingDirectory: '$(Build.SourcesDirectory)' - retryCountOnTaskFailure: 2 - displayName: 'Bootstrap' - env: - __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) - - - pwsh: | - Import-Module .\build.psm1 -force - Find-DotNet - dotnet tool install dotnet-symbol --tool-path $(Agent.ToolsDirectory)\tools\dotnet-symbol - $symbolToolPath = Get-ChildItem -Path $(Agent.ToolsDirectory)\tools\dotnet-symbol\dotnet-symbol.exe | Select-Object -First 1 -ExpandProperty FullName - Write-Host "##vso[task.setvariable variable=symbolToolPath]$symbolToolPath" - displayName: Install dotnet-symbol - retryCountOnTaskFailure: 2 - - - pwsh: | - Import-module '$(BUILD.SOURCESDIRECTORY)/build.psm1' - Install-AzCopy - displayName: Install AzCopy - retryCountOnTaskFailure: 2 - - - pwsh: | - Import-module '$(BUILD.SOURCESDIRECTORY)/build.psm1' - $azcopy = Find-AzCopy - Write-Verbose -Verbose "Found AzCopy: $azcopy" - - $winverifySymbolsPath = New-Item -ItemType Directory -Path '$(System.ArtifactsDirectory)/winverify-symbols' -Force - Write-Host "##vso[task.setvariable variable=winverifySymbolsPath]$winverifySymbolsPath" - - & $azcopy cp https://$(StorageAccount).blob.core.windows.net/winverify-private $winverifySymbolsPath --recursive - - Get-ChildItem $winverifySymbolsPath -Recurse | Out-String | Write-Verbose -Verbose - - displayName: Download winverify-private Artifacts - retryCountOnTaskFailure: 2 - env: - AZCOPY_AUTO_LOGIN_TYPE: MSI - - - pwsh: | - Import-Module .\build.psm1 -force - Find-DotNet - Start-PSBuild -Configuration StaticAnalysis -PSModuleRestore -Clean -Runtime fxdependent-win-desktop - - $OutputFolder = Split-Path (Get-PSOutput) - Write-Host "##vso[task.setvariable variable=BinDir]$OutputFolder" - - Write-Verbose -Verbose -Message "Deleting ref folder from output folder" - if (Test-Path $OutputFolder/ref) { - Remove-Item -Recurse -Force $OutputFolder/ref - } - workingDirectory: '$(Build.SourcesDirectory)' - displayName: 'Build PowerShell Source' - - - pwsh: | - Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose - displayName: Capture Environment - condition: succeededOrFailed() - - # Explicitly download symbols for the drop since the SDL image doesn't have http://SymWeb access and APIScan cannot handle https yet. - - pwsh: | - Import-Module .\build.psm1 -force - Find-DotNet - $pat = '$(SymbolServerPAT)' - if ($pat -like '*PAT*' -or $pat -eq '') - { - throw 'No PAT defined' - } - $url = 'https://microsoft.artifacts.visualstudio.com/defaultcollection/_apis/symbol/symsrv' - $(symbolToolPath) --authenticated-server-path $(SymbolServerPAT) $url --symbols -d "$env:BinDir\*" --recurse-subdirectories - displayName: 'Download Symbols for binaries' - retryCountOnTaskFailure: 2 - workingDirectory: '$(Build.SourcesDirectory)' - - - pwsh: | - Get-ChildItem '$(BinDir)' -File -Recurse | - Foreach-Object { - [pscustomobject]@{ - Path = $_.FullName - Version = $_.VersionInfo.FileVersion - Md5Hash = (Get-FileHash -Algorithm MD5 -Path $_.FullName).Hash - Sha512Hash = (Get-FileHash -Algorithm SHA512 -Path $_.FullName).Hash - } - } | Export-Csv -Path '$(Build.SourcesDirectory)/ReleaseFileHash.csv' - displayName: 'Create release file hash artifact' - - - task: PublishBuildArtifacts@1 - displayName: 'Publish Build File Hash artifact' - inputs: - pathToPublish: '$(Build.SourcesDirectory)/ReleaseFileHash.csv' - artifactName: ReleaseFilesHash - retryCountOnTaskFailure: 2 - - - task: securedevelopmentteam.vss-secure-development-tools.build-task-apiscan.APIScan@2 - displayName: 'Run APIScan' - inputs: - softwareFolder: '$(BinDir)' - softwareName: PowerShell - softwareVersionNum: '$(ReleaseTagVar)' - isLargeApp: false - preserveTempFiles: false - verbosityLevel: standard - # write a status update every 5 minutes. Default is 1 minute - statusUpdateInterval: '00:05:00' - env: - AzureServicesAuthConnectionString: RunAs=App - - - task: securedevelopmentteam.vss-secure-development-tools.build-task-report.SdtReport@2 - continueOnError: true - displayName: 'Guardian Export' - inputs: - GdnExportVstsConsole: true - GdnExportSarifFile: true - GdnExportHtmlFile: true - GdnExportAllTools: false - GdnExportGdnToolApiScan: true - #this didn't do anything GdnExportCustomLogsFolder: '$(Build.ArtifactStagingDirectory)/Guardian' - - - task: TSAUpload@2 - displayName: 'TSA upload' - inputs: - GdnPublishTsaOnboard: false - GdnPublishTsaConfigFile: '$(Build.SourcesDirectory)\tools\guardian\tsaconfig-APIScan.json' - - - pwsh: | - Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose - displayName: Capture Environment - condition: succeededOrFailed() - - - task: securedevelopmentteam.vss-secure-development-tools.build-task-publishsecurityanalysislogs.PublishSecurityAnalysisLogs@3 - displayName: 'Publish Guardian Artifacts' - inputs: - AllTools: false - APIScan: true - ArtifactName: APIScan diff --git a/tools/releaseBuild/azureDevOps/templates/compliance/compliance.yml b/tools/releaseBuild/azureDevOps/templates/compliance/compliance.yml deleted file mode 100644 index 8db52fc83f0..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/compliance/compliance.yml +++ /dev/null @@ -1,83 +0,0 @@ -parameters: - - name: parentJobs - type: jobList - -jobs: -- job: compliance - variables: - - name: runCodesignValidationInjection - value : false - - name: NugetSecurityAnalysisWarningLevel - value: none - - # Defines the variables APIScanClient, APIScanTenant and APIScanSecret - - group: PS-PS-APIScan - - displayName: Compliance - dependsOn: - ${{ parameters.parentJobs }} - pool: - name: $(windowsPool) - demands: - - ImageOverride -equals PSMMS2019-Secure - - # APIScan can take a long time - timeoutInMinutes: 180 - - steps: - - checkout: self - clean: true - - - task: securedevelopmentteam.vss-secure-development-tools.build-task-credscan.CredScan@3 - displayName: 'Run CredScan' - inputs: - suppressionsFile: tools/credScan/suppress.json - debugMode: false - continueOnError: true - - - task: securedevelopmentteam.vss-secure-development-tools.build-task-policheck.PoliCheck@2 - displayName: 'Run PoliCheck' - inputs: - # targetType F means file or folder and is the only applicable value and the default - targetType: F - # 1 to enable source code comment scanning, which is what we should do for open source - optionsFC: 1 - # recurse - optionsXS: 1 - # run for severity 1, 2, 3 and 4 issues - optionsPE: '1|2|3|4' - # disable history management - optionsHMENABLE: 0 - # Excluclusion access database - optionsRulesDBPath: '$(Build.SourcesDirectory)\tools\terms\PowerShell-Terms-Rules.mdb' - # Terms Exclusion xml file - optionsUEPath: $(Build.SourcesDirectory)\tools\terms\TermsExclusion.xml - continueOnError: true - - - task: securedevelopmentteam.vss-secure-development-tools.build-task-publishsecurityanalysislogs.PublishSecurityAnalysisLogs@3 - displayName: 'Publish Security Analysis Logs to Build Artifacts' - continueOnError: true - - - task: TSAUpload@2 - displayName: 'TSA upload' - inputs: - GdnPublishTsaOnboard: false - GdnPublishTsaConfigFile: '$(Build.SourcesDirectory)\tools\guardian\tsaconfig-others.json' - - - task: securedevelopmentteam.vss-secure-development-tools.build-task-report.SdtReport@1 - displayName: 'Create Security Analysis Report' - inputs: - TsvFile: false - APIScan: false - BinSkim: false - CredScan: true - PoliCheck: true - PoliCheckBreakOn: Severity2Above - - - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 - displayName: 'Component Detection' - inputs: - sourceScanPath: '$(Build.SourcesDirectory)\tools' - snapshotForceEnabled: true - - - template: /tools/releaseBuild/azureDevOps/templates/step/finalize.yml diff --git a/tools/releaseBuild/azureDevOps/templates/compliance/generateNotice.yml b/tools/releaseBuild/azureDevOps/templates/compliance/generateNotice.yml deleted file mode 100644 index 3e91b9174d2..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/compliance/generateNotice.yml +++ /dev/null @@ -1,90 +0,0 @@ -parameters: - - name: parentJobs - type: jobList - -jobs: -- job: generateNotice - variables: - - name: runCodesignValidationInjection - value : false - - name: NugetSecurityAnalysisWarningLevel - value: none - - displayName: Generate Notice - dependsOn: - ${{ parameters.parentJobs }} - pool: - name: PowerShell1ES - demands: - - ImageOverride -equals PSMMS2019-Secure - - timeoutInMinutes: 15 - - steps: - - checkout: self - clean: true - - - pwsh: | - [string]$Branch=$env:BUILD_SOURCEBRANCH - $branchOnly = $Branch -replace '^refs/heads/'; - $branchOnly = $branchOnly -replace '[_\-]' - - if ($branchOnly -eq 'master') { - $container = 'tpn' - } else { - $branchOnly = $branchOnly -replace '[\./]', '-' - $container = "tpn-$branchOnly" - } - - $vstsCommandString = "vso[task.setvariable variable=tpnContainer]$container" - Write-Verbose -Message $vstsCommandString -Verbose - Write-Host -Object "##$vstsCommandString" - displayName: Set ContainerName - - - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 - displayName: 'Component Detection' - inputs: - sourceScanPath: '$(Build.SourcesDirectory)\tools' - - - pwsh: | - ./tools/clearlyDefined/ClearlyDefined.ps1 -TestAndHarvest - displayName: Verify that packages have license data - - - task: msospo.ospo-extension.8d7f9abb-6896-461d-9e25-4f74ed65ddb2.notice@0 - displayName: 'NOTICE File Generator' - inputs: - outputfile: '$(System.ArtifactsDirectory)\ThirdPartyNotices.txt' - # output format can be html or text - outputformat: text - # this isn't working - # additionaldata: $(Build.SourcesDirectory)\assets\additionalAttributions.txt - - - - pwsh: | - Get-Content -Raw -Path $(Build.SourcesDirectory)\assets\additionalAttributions.txt | Out-File '$(System.ArtifactsDirectory)\ThirdPartyNotices.txt' -Encoding utf8NoBOM -Force -Append - Get-Content -Raw -Path '$(Build.SourcesDirectory)\assets\additionalAttributions.txt' - displayName: Append Additional Attributions - continueOnError: true - - - pwsh: | - Get-Content -Raw -Path '$(System.ArtifactsDirectory)\ThirdPartyNotices.txt' - displayName: Capture Notice - continueOnError: true - - - task: AzureFileCopy@4 - displayName: 'upload Notice' - inputs: - SourcePath: $(System.ArtifactsDirectory)\ThirdPartyNotices.txt - azureSubscription: '$(AzureFileCopySubscription)' - Destination: AzureBlob - storage: '$(StorageAccount)' - ContainerName: $(tpnContainer) - resourceGroup: '$(StorageResourceGroup)' - retryCountOnTaskFailure: 2 - - - task: PublishPipelineArtifact@1 - inputs: - targetPath: $(System.ArtifactsDirectory) - artifactName: notice - displayName: Publish notice artifacts - retryCountOnTaskFailure: 2 diff --git a/tools/releaseBuild/azureDevOps/templates/expand-compliance.yml b/tools/releaseBuild/azureDevOps/templates/expand-compliance.yml deleted file mode 100644 index 4cc25433262..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/expand-compliance.yml +++ /dev/null @@ -1,12 +0,0 @@ -parameters: - architecture: x86 - version: 6.2.0 - -steps: - - powershell: | - Expand-Archive -Path "$(System.ArtifactsDirectory)\results\PowerShell-${{ parameters.version }}-symbols-win-${{ parameters.architecture }}.zip" -Destination "$(Build.StagingDirectory)\symbols\${{ parameters.architecture }}" - displayName: Expand symbols zip - ${{ parameters.architecture }} - - - powershell: | - tools/releaseBuild/createComplianceFolder.ps1 -ArtifactFolder "$(Build.StagingDirectory)\symbols\${{ parameters.architecture }}" -VSTSVariableName 'CompliancePath' - displayName: Expand Compliance file - ${{ parameters.architecture }} diff --git a/tools/releaseBuild/azureDevOps/templates/global-tool-pkg-sbom.yml b/tools/releaseBuild/azureDevOps/templates/global-tool-pkg-sbom.yml deleted file mode 100644 index 5cdf5675079..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/global-tool-pkg-sbom.yml +++ /dev/null @@ -1,64 +0,0 @@ -parameters: - - name: PackageVersion - - name: LinuxBinPath - - name: WindowsBinPath - - name: WindowsDesktopBinPath - - name: AlpineBinPath - - name: DestinationPath - - name: ListOfPackageTypes - type: object - default: - - Unified - - PowerShell.Linux.Alpine - - PowerShell.Linux.x64 - - PowerShell.Linux.arm32 - - PowerShell.Linux.arm64 - - PowerShell.Windows.x64 - -steps: - -- pwsh: | - Write-Verbose -Verbose 'LinuxBinPath path: ${{ parameters.LinuxBinPath }}' - Write-Verbose -Verbose 'WindowsBinPath path: ${{ parameters.WindowsBinPath }}' - Write-Verbose -Verbose 'WindowsDesktopBinPath path: ${{ parameters.WindowsDesktopBinPath }}' - Write-Verbose -Verbose 'AlpineBinPath path: ${{ parameters.AlpineBinPath }}' - - Import-Module -Name $env:REPOROOT\build.psm1 - Import-Module -Name $env:REPOROOT\tools\packaging - Start-PrepForGlobalToolNupkg -LinuxBinPath '${{ parameters.LinuxBinPath }}' -WindowsBinPath '${{ parameters.WindowsBinPath }}' -WindowsDesktopBinPath '${{ parameters.WindowsDesktopBinPath }}' -AlpineBinPath '${{ parameters.AlpineBinPath }}' - displayName: 'Preparation for Global Tools package creation.' - -# NOTE: The Unified package must always be created first, and so must always be first in ListOfPackageTypes. -- ${{ each value in parameters.ListOfPackageTypes }}: - - pwsh: | - $PackageType = '${{ value }}' - - Write-Verbose -Verbose "PackageType: $PackageType" - Write-Verbose -Verbose 'Destination path: ${{ parameters.PackagePath }}' - - # Create global tool NuSpec source for package. - Import-Module -Name $env:REPOROOT\build.psm1 - Import-Module -Name $env:REPOROOT\tools\packaging - New-GlobalToolNupkgSource -PackageType $PackageType -PackageVersion '${{ parameters.PackageVersion }}' -LinuxBinPath '${{ parameters.LinuxBinPath }}' -WindowsBinPath '${{ parameters.WindowsBinPath }}' -WindowsDesktopBinPath '${{ parameters.WindowsDesktopBinPath }}' -AlpineBinPath '${{ parameters.AlpineBinPath }}' - displayName: 'Create global tool NuSpec source for package.' - - - pwsh: | - Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose - displayName: 'Capture environment variables after Global Tool package source is created.' - - # NOTE: The above 'New-GlobalToolNupkgSource' task function sets the 'GlobalToolNuSpecSourcePath', 'GlobalToolPkgName', - # and 'GlobalToolCGManifestPath' environment variables. - - template: Sbom.yml@ComplianceRepo - parameters: - BuildDropPath: $(GlobalToolNuSpecSourcePath) - Build_Repository_Uri: 'https://github.com/powershell/powershell' - PackageName: $(GlobalToolPkgName) - PackageVersion: ${{ parameters.PackageVersion }} - sourceScanPath: $(GlobalToolCGManifestPath) - displayName: SBOM for Global Tool package - - - pwsh: | - Import-Module -Name $env:REPOROOT\build.psm1 - Import-Module -Name $env:REPOROOT\tools\packaging - New-GlobalToolNupkgFromSource -PackageNuSpecPath "$env:GlobalToolNuSpecSourcePath" -PackageName "$env:GlobalToolPkgName" -DestinationPath '${{ parameters.DestinationPath }}' -CGManifestPath "$env:GlobalToolCGManifestPath" - displayName: 'Create global tool NuSpec package from NuSpec source.' diff --git a/tools/releaseBuild/azureDevOps/templates/insert-nuget-config-azfeed.yml b/tools/releaseBuild/azureDevOps/templates/insert-nuget-config-azfeed.yml deleted file mode 100644 index 61b9df6c342..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/insert-nuget-config-azfeed.yml +++ /dev/null @@ -1,8 +0,0 @@ -parameters: -- name: "repoRoot" - default: $(REPOROOT) -steps: - - template: /.pipelines/templates/insert-nuget-config-azfeed.yml@self - parameters: - repoRoot: $(REPOROOT) - diff --git a/tools/releaseBuild/azureDevOps/templates/json.yml b/tools/releaseBuild/azureDevOps/templates/json.yml deleted file mode 100644 index 48a50e0bf14..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/json.yml +++ /dev/null @@ -1,57 +0,0 @@ -parameters: - parentJobs: [] - -jobs: -- job: json - variables: - - name: runCodesignValidationInjection - value : false - - name: NugetSecurityAnalysisWarningLevel - value: none - displayName: Create Json for Blob - dependsOn: - ${{ parameters.parentJobs }} - condition: succeeded() - pool: - name: $(windowsPool) - demands: - - ImageOverride -equals PSMMS2019-Secure - - steps: - #- task: <task type name>@<version> - # inputs: - # <task specific inputs> - # displayName: '<display name of task>' - - checkout: self - clean: true - - - template: SetVersionVariables.yml - parameters: - ReleaseTagVar: $(ReleaseTagVar) - CreateJson: yes - - - task: AzureFileCopy@4 - displayName: 'upload daily-build-info JSON file to Azure - ${{ parameters.architecture }}' - inputs: - SourcePath: '$(BuildInfoPath)' - azureSubscription: '$(AzureFileCopySubscription)' - Destination: AzureBlob - storage: '$(StorageAccount)' - ContainerName: 'BuildInfo' - condition: and(succeeded(), eq(variables['IS_DAILY'], 'true')) - - - task: AzureCLI@1 - displayName: 'Make blob public' - inputs: - azureSubscription: '$(AzureFileCopySubscription)' - scriptLocation: inlineScript - inlineScript: 'az storage container set-permission --account-name $(StorageAccount) --name $(azureVersion) --public-access blob' - condition: and(succeeded(), eq(variables['IS_DAILY'], 'true')) - - - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 - displayName: 'Component Detection' - inputs: - sourceScanPath: '$(Build.SourcesDirectory)\tools' - snapshotForceEnabled: true - - - template: /tools/releaseBuild/azureDevOps/templates/step/finalize.yml diff --git a/tools/releaseBuild/azureDevOps/templates/linux-authenticode-sign.yml b/tools/releaseBuild/azureDevOps/templates/linux-authenticode-sign.yml deleted file mode 100644 index 719ba1a6c30..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/linux-authenticode-sign.yml +++ /dev/null @@ -1,184 +0,0 @@ -jobs: -- job: sign_linux_builds - displayName: Sign all linux builds - condition: succeeded() - pool: - name: PowerShell1ES - demands: - - ImageOverride -equals PSMMS2019-Secure - dependsOn: ['build_fxdependent', 'build_rpm'] - variables: - - name: runCodesignValidationInjection - value: false - - name: NugetSecurityAnalysisWarningLevel - value: none - - group: ESRP - - steps: - - checkout: self - clean: true - - - task: DownloadPipelineArtifact@2 - inputs: - artifact: pwshLinuxBuild.tar.gz - path: $(Build.ArtifactStagingDirectory)/linuxTars - displayName: Download deb build - - - task: DownloadPipelineArtifact@2 - inputs: - artifact: pwshLinuxBuildMinSize.tar.gz - path: $(Build.ArtifactStagingDirectory)/linuxTars - displayName: Download min-size build - - - task: DownloadPipelineArtifact@2 - inputs: - artifact: pwshLinuxBuildArm32.tar.gz - path: $(Build.ArtifactStagingDirectory)/linuxTars - displayName: Download arm32 build - - - task: DownloadPipelineArtifact@2 - inputs: - artifact: pwshLinuxBuildArm64.tar.gz - path: $(Build.ArtifactStagingDirectory)/linuxTars - displayName: Download arm64 build - - - task: DownloadPipelineArtifact@2 - inputs: - artifact: pwshMarinerBuildAmd64.tar.gz - path: $(Build.ArtifactStagingDirectory)/linuxTars - displayName: Download mariner build - - - task: DownloadPipelineArtifact@2 - inputs: - artifact: pwshMarinerBuildArm64.tar.gz - path: $(Build.ArtifactStagingDirectory)/linuxTars - displayName: Download mariner arm64 build - - - task: DownloadPipelineArtifact@2 - inputs: - artifact: pwshLinuxBuildAlpine.tar.gz - path: $(Build.ArtifactStagingDirectory)/linuxTars/pwshLinuxBuildAlpine.tar.gz - displayName: Download alpine build - - - task: DownloadPipelineArtifact@2 - inputs: - artifact: pwshLinuxBuildAlpine.tar.gz - path: $(Build.ArtifactStagingDirectory)/linuxTars/pwshAlpineFxdBuildAmd64.tar.gz - displayName: Download alpine fxdependent build - - - task: DownloadPipelineArtifact@2 - inputs: - artifact: pwshLinuxBuildFxdependent.tar.gz - path: $(Build.ArtifactStagingDirectory)/linuxTars/pwshLinuxBuildFxdependent.tar.gz - displayName: Download fxdependent build - - - pwsh: | - Get-ChildItem -Path $(Build.ArtifactStagingDirectory)/linuxTars - displayName: Capture downloaded tars - - - pwsh: | - Write-Verbose -Verbose -Message "Expanding $(Build.ArtifactStagingDirectory)/linuxTars/pwshLinuxBuild.tar.gz to $(Build.ArtifactStagingDirectory)/pwshLinuxBuild" - New-Item -Path $(Build.ArtifactStagingDirectory)/pwshLinuxBuild -ItemType Directory - tar -xf $(Build.ArtifactStagingDirectory)/linuxTars/pwshLinuxBuild.tar.gz -C $(Build.ArtifactStagingDirectory)/pwshLinuxBuild - Write-Verbose -Verbose "File permisions after expanding" - Get-ChildItem -Path $(Build.ArtifactStagingDirectory)/pwshLinuxBuild/pwsh | Select-Object -Property 'unixmode', 'size', 'name' - - Write-Verbose -Verbose -Message "Expanding $(Build.ArtifactStagingDirectory)/linuxTars/pwshLinuxBuildMinSize.tar.gz to $(Build.ArtifactStagingDirectory)/pwshLinuxBuildMinSize" - New-Item -Path $(Build.ArtifactStagingDirectory)/pwshLinuxBuildMinSize -ItemType Directory - tar -xf $(Build.ArtifactStagingDirectory)/linuxTars/pwshLinuxBuildMinSize.tar.gz -C $(Build.ArtifactStagingDirectory)/pwshLinuxBuildMinSize - - Write-Verbose -Verbose -Message "Expanding $(Build.ArtifactStagingDirectory)/linuxTars/pwshLinuxBuildArm32.tar.gz to $(Build.ArtifactStagingDirectory)/pwshLinuxBuildArm32" - New-Item -Path $(Build.ArtifactStagingDirectory)/pwshLinuxBuildArm32 -ItemType Directory - tar -xf $(Build.ArtifactStagingDirectory)/linuxTars/pwshLinuxBuildArm32.tar.gz -C $(Build.ArtifactStagingDirectory)/pwshLinuxBuildArm32 - - Write-Verbose -Verbose -Message "Expanding $(Build.ArtifactStagingDirectory)/linuxTars/pwshLinuxBuildArm64.tar.gz to $(Build.ArtifactStagingDirectory)/pwshLinuxBuildArm64" - New-Item -Path $(Build.ArtifactStagingDirectory)/pwshLinuxBuildArm64 -ItemType Directory - tar -xf $(Build.ArtifactStagingDirectory)/linuxTars/pwshLinuxBuildArm64.tar.gz -C $(Build.ArtifactStagingDirectory)/pwshLinuxBuildArm64 - - Write-Verbose -Verbose -Message "Expanding $(Build.ArtifactStagingDirectory)/linuxTars/pwshMarinerBuildAmd64.tar.gz to $(Build.ArtifactStagingDirectory)/pwshMarinerBuildAmd64" - New-Item -Path $(Build.ArtifactStagingDirectory)/pwshMarinerBuildAmd64 -ItemType Directory - tar -xf $(Build.ArtifactStagingDirectory)/linuxTars/pwshMarinerBuildAmd64.tar.gz -C $(Build.ArtifactStagingDirectory)/pwshMarinerBuildAmd64 - - Write-Verbose -Verbose -Message "Expanding $(Build.ArtifactStagingDirectory)/linuxTars/pwshMarinerBuildArm64.tar.gz to $(Build.ArtifactStagingDirectory)/pwshMarinerBuildArm64" - New-Item -Path $(Build.ArtifactStagingDirectory)/pwshMarinerBuildArm64 -ItemType Directory - tar -xf $(Build.ArtifactStagingDirectory)/linuxTars/pwshMarinerBuildArm64.tar.gz -C $(Build.ArtifactStagingDirectory)/pwshMarinerBuildArm64 - - Write-Verbose -Verbose -Message "Expanding $(Build.ArtifactStagingDirectory)/linuxTars/pwshLinuxBuildAlpine.tar.gz/pwshLinuxBuild.tar.gz to $(Build.ArtifactStagingDirectory)/pwshLinuxBuildAlpine" - New-Item -Path $(Build.ArtifactStagingDirectory)/pwshLinuxBuildAlpine -ItemType Directory - tar -xf $(Build.ArtifactStagingDirectory)/linuxTars/pwshLinuxBuildAlpine.tar.gz/pwshLinuxBuild.tar.gz -C $(Build.ArtifactStagingDirectory)/pwshLinuxBuildAlpine - - Write-Verbose -Verbose -Message "Expanding $(Build.ArtifactStagingDirectory)/linuxTars/pwshAlpineFxdBuildAmd64.tar.gz/pwshAlpineFxdBuildAmd64.tar.gz to $(Build.ArtifactStagingDirectory)/pwshLinuxBuildAlpineFxd" - New-Item -Path $(Build.ArtifactStagingDirectory)/pwshLinuxBuildAlpineFxd -ItemType Directory - tar -xf $(Build.ArtifactStagingDirectory)/linuxTars/pwshAlpineFxdBuildAmd64.tar.gz/pwshAlpineFxdBuildAmd64.tar.gz -C $(Build.ArtifactStagingDirectory)/pwshLinuxBuildAlpineFxd - - Write-Verbose -Verbose -Message "Expanding $(Build.ArtifactStagingDirectory)/linuxTars/pwshLinuxBuildFxdependent.tar.gz/pwshLinuxBuild.tar.gz to $(Build.ArtifactStagingDirectory)/pwshLinuxBuildFxdependent" - New-Item -Path $(Build.ArtifactStagingDirectory)/pwshLinuxBuildFxdependent -ItemType Directory - tar -xf $(Build.ArtifactStagingDirectory)/linuxTars/pwshLinuxBuildFxdependent.tar.gz/pwshLinuxBuild.tar.gz -C $(Build.ArtifactStagingDirectory)/pwshLinuxBuildFxdependent - displayName: Expand builds - - - template: SetVersionVariables.yml - parameters: - ReleaseTagVar: $(ReleaseTagVar) - - - template: cloneToOfficialPath.yml - - - template: insert-nuget-config-azfeed.yml - parameters: - repoRoot: $(PowerShellRoot) - - - pwsh: | - Set-Location $env:POWERSHELLROOT - import-module "$env:POWERSHELLROOT/build.psm1" - Sync-PSTags -AddRemoteIfMissing - displayName: SyncTags - condition: and(succeeded(), ne(variables['SkipBuild'], 'true')) - - - checkout: ComplianceRepo - clean: true - - - template: shouldSign.yml - - - template: signBuildFiles.yml - parameters: - binLocation: pwshLinuxBuild - buildPrefixName: 'PowerShell Linux' - - - template: signBuildFiles.yml - parameters: - binLocation: pwshLinuxBuildMinSize - buildPrefixName: 'PowerShell Linux Minimum Size' - - - template: signBuildFiles.yml - parameters: - binLocation: pwshLinuxBuildArm32 - buildPrefixName: 'PowerShell Linux Arm32' - - - template: signBuildFiles.yml - parameters: - binLocation: pwshLinuxBuildArm64 - buildPrefixName: 'PowerShell Linux Arm64' - - - template: signBuildFiles.yml - parameters: - binLocation: pwshMarinerBuildAmd64 - buildPrefixName: 'PowerShell Linux x64 (Mariner) Framework Dependent' - - - template: signBuildFiles.yml - parameters: - binLocation: pwshMarinerBuildArm64 - buildPrefixName: 'PowerShell Linux arm64 (Mariner) Framework Dependent' - - - template: signBuildFiles.yml - parameters: - binLocation: pwshLinuxBuildAlpine - buildPrefixName: 'PowerShell Linux Alpine x64' - - - template: signBuildFiles.yml - parameters: - binLocation: pwshLinuxBuildAlpineFxd - buildPrefixName: 'PowerShell Linux Alpine Fxd x64' - - - template: signBuildFiles.yml - parameters: - binLocation: pwshLinuxBuildFxdependent - buildPrefixName: 'PowerShell Linux Framework Dependent' diff --git a/tools/releaseBuild/azureDevOps/templates/linux-packaging.yml b/tools/releaseBuild/azureDevOps/templates/linux-packaging.yml deleted file mode 100644 index 59db37c64ac..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/linux-packaging.yml +++ /dev/null @@ -1,489 +0,0 @@ -parameters: - buildName: '' - uploadDisplayName: 'Upload' - -jobs: -- job: pkg_${{ parameters.buildName }} - displayName: Package ${{ parameters.buildName }} - condition: succeeded() - pool: - name: PowerShell1ES - demands: - - ImageOverride -equals PSMMSUbuntu20.04-Secure - variables: - - name: runCodesignValidationInjection - value: false - - name: build - value: ${{ parameters.buildName }} - - name: NugetSecurityAnalysisWarningLevel - value: none - - group: ESRP - - group: DotNetPrivateBuildAccess - - steps: - - ${{ if or(eq(variables.build,'deb'), eq(variables.build,'rpm')) }} : - - task: DownloadPipelineArtifact@2 - inputs: - artifact: authenticode-signed - path: $(Build.ArtifactStagingDirectory)/pwshLinuxBuild-signed - pattern: '**/pwshLinuxBuild.tar.gz' - displayName: Download deb build - - - ${{ if eq(variables.build,'deb') }} : - - task: DownloadPipelineArtifact@2 - inputs: - artifact: authenticode-signed - path: $(Build.ArtifactStagingDirectory)/pwshLinuxBuildMinSize-signed - pattern: '**/pwshLinuxBuildMinSize.tar.gz' - displayName: Download min-size build - - - ${{ if eq(variables.build,'deb') }} : - - task: DownloadPipelineArtifact@2 - inputs: - artifact: authenticode-signed - path: $(Build.ArtifactStagingDirectory)/pwshLinuxBuildArm32-signed - pattern: '**/pwshLinuxBuildArm32.tar.gz' - displayName: Download arm32 build - - - ${{ if eq(variables.build,'deb') }} : - - task: DownloadPipelineArtifact@2 - inputs: - artifact: authenticode-signed - path: $(Build.ArtifactStagingDirectory)/pwshLinuxBuildArm64-signed - pattern: '**/pwshLinuxBuildArm64.tar.gz' - displayName: Download arm64 build - - - ${{ if eq(variables.build,'rpm') }} : - - task: DownloadPipelineArtifact@2 - inputs: - artifact: authenticode-signed - path: $(Build.ArtifactStagingDirectory)/pwshMarinerBuildAmd64-signed - pattern: '**/pwshMarinerBuildAmd64.tar.gz' - displayName: Download mariner amd64 build - - - ${{ if eq(variables.build,'rpm') }} : - - task: DownloadPipelineArtifact@2 - inputs: - artifact: authenticode-signed - path: $(Build.ArtifactStagingDirectory)/pwshMarinerBuildArm64-signed - pattern: '**/pwshMarinerBuildArm64.tar.gz' - displayName: Download mariner arm64 build - - - ${{ if eq(variables.build,'alpine') }} : - - task: DownloadPipelineArtifact@2 - inputs: - artifact: authenticode-signed - path: $(Build.ArtifactStagingDirectory)/pwshLinuxBuildAlpine-signed - pattern: '**/pwshLinuxBuildAlpine.tar.gz' - displayName: Download alpine build - - - ${{ if eq(variables.build,'alpine') }} : - - task: DownloadPipelineArtifact@2 - inputs: - artifact: authenticode-signed - path: $(Build.ArtifactStagingDirectory)/pwshAlpineFxdBuildAmd64-signed - pattern: '**/pwshAlpineFxdBuildAmd64.tar.gz' - displayName: Download alpine framework dependent build - - - ${{ if eq(variables.build,'fxdependent') }} : - - task: DownloadPipelineArtifact@2 - inputs: - artifact: authenticode-signed - path: $(Build.ArtifactStagingDirectory)/pwshLinuxBuildFxdependent-signed - pattern: '**/pwshLinuxBuildFxdependent.tar.gz' - displayName: Download fxdependent build - - - ${{ if or(eq(variables.build,'deb'), eq(variables.build,'rpm')) }} : - - task: DownloadPipelineArtifact@2 - inputs: - artifact: pwshLinuxBuild-meta - path: $(Build.ArtifactStagingDirectory)/pwshLinuxBuild-meta - displayName: Download deb build meta - - - ${{ if eq(variables.build,'deb') }} : - - task: DownloadPipelineArtifact@2 - inputs: - artifact: pwshLinuxBuildMinSize-meta - path: $(Build.ArtifactStagingDirectory)/pwshLinuxBuildMinSize-meta - displayName: Download min-size build meta - - - ${{ if eq(variables.build,'deb') }} : - - task: DownloadPipelineArtifact@2 - inputs: - artifact: pwshLinuxBuildArm32-meta - path: $(Build.ArtifactStagingDirectory)/pwshLinuxBuildArm32-meta - displayName: Download arm32 build meta - - - ${{ if eq(variables.build,'deb') }} : - - task: DownloadPipelineArtifact@2 - inputs: - artifact: pwshLinuxBuildArm64-meta - path: $(Build.ArtifactStagingDirectory)/pwshLinuxBuildArm64-meta - displayName: Download arm64 build meta - - - ${{ if eq(variables.build,'rpm') }} : - - task: DownloadPipelineArtifact@2 - inputs: - artifact: pwshMarinerBuildAmd64-meta - path: $(Build.ArtifactStagingDirectory)/pwshMarinerBuildAmd64-meta - displayName: Download mariner x64 build meta - - - ${{ if eq(variables.build,'rpm') }} : - - task: DownloadPipelineArtifact@2 - inputs: - artifact: pwshMarinerBuildArm64-meta - path: $(Build.ArtifactStagingDirectory)/pwshMarinerBuildArm64-meta - displayName: Download mariner arm64 build meta - - - ${{ if eq(variables.build,'alpine') }} : - - task: DownloadPipelineArtifact@2 - inputs: - artifact: pwshLinuxBuildAlpine-meta - path: $(Build.ArtifactStagingDirectory)/pwshLinuxBuild-meta - displayName: Download alpine build meta - - - ${{ if eq(variables.build,'alpine') }} : - - task: DownloadPipelineArtifact@2 - inputs: - artifact: pwshAlpineFxdBuildAmd64-meta - path: $(Build.ArtifactStagingDirectory)/pwshAlpineFxdBuildAmd64-meta - displayName: Download alpine build meta - - - ${{ if eq(variables.build,'fxdependent') }} : - - task: DownloadPipelineArtifact@2 - inputs: - artifact: pwshLinuxBuildFxdependent-meta - path: $(Build.ArtifactStagingDirectory)/pwshLinuxBuild-meta - displayName: Download fxdependent build meta - - - pwsh: | - Get-ChildItem '$(Build.ArtifactStagingDirectory)' | Select-Object -Property 'unixmode', 'size', 'name' - displayName: Capture downloads - - - pwsh: | - if ('$(build)' -eq 'deb' -or '$(build)' -eq 'rpm') { - Write-Verbose -Verbose "Expanding $(Build.ArtifactStagingDirectory)/pwshLinuxBuild-signed/pwshLinuxBuild.tar.gz to $(Build.ArtifactStagingDirectory)/pwshLinuxBuild" - New-Item -Path $(Build.ArtifactStagingDirectory)/pwshLinuxBuild -ItemType Directory - tar -xf $(Build.ArtifactStagingDirectory)/pwshLinuxBuild-signed/pwshLinuxBuild.tar.gz -C $(Build.ArtifactStagingDirectory)/pwshLinuxBuild - } - - if ('$(build)' -eq 'deb') { - Write-Verbose -Verbose "Expanding $(Build.ArtifactStagingDirectory)/pwshLinuxBuildMinSize-signed/pwshLinuxBuildMinSize.tar.gz to $(Build.ArtifactStagingDirectory)/pwshLinuxBuildMinSize" - New-Item -Path $(Build.ArtifactStagingDirectory)/pwshLinuxBuildMinSize -ItemType Directory - tar -xf $(Build.ArtifactStagingDirectory)/pwshLinuxBuildMinSize-signed/pwshLinuxBuildMinSize.tar.gz -C $(Build.ArtifactStagingDirectory)/pwshLinuxBuildMinSize - - Write-Verbose -Verbose "Expanding $(Build.ArtifactStagingDirectory)/pwshLinuxBuildArm32-signed/pwshLinuxBuildArm32.tar.gz to $(Build.ArtifactStagingDirectory)/pwshLinuxBuildArm32" - New-Item -Path $(Build.ArtifactStagingDirectory)/pwshLinuxBuildArm32 -ItemType Directory - tar -xf $(Build.ArtifactStagingDirectory)/pwshLinuxBuildArm32-signed/pwshLinuxBuildArm32.tar.gz -C $(Build.ArtifactStagingDirectory)/pwshLinuxBuildArm32 - - Write-Verbose -Verbose "Expanding $(Build.ArtifactStagingDirectory)/pwshLinuxBuildArm64-signed/pwshLinuxBuildArm64.tar.gz to $(Build.ArtifactStagingDirectory)/pwshLinuxBuildArm64" - New-Item -Path $(Build.ArtifactStagingDirectory)/pwshLinuxBuildArm64 -ItemType Directory - tar -xf $(Build.ArtifactStagingDirectory)/pwshLinuxBuildArm64-signed/pwshLinuxBuildArm64.tar.gz -C $(Build.ArtifactStagingDirectory)/pwshLinuxBuildArm64 - } - - if ('$(build)' -eq 'rpm') { - # for mariner x64 - Write-Verbose -Verbose "Expanding $(Build.ArtifactStagingDirectory)/pwshMarinerBuildAmd64-signed/pwshMarinerBuildAmd64.tar.gz to $(Build.ArtifactStagingDirectory)/pwshMarinerBuildAmd64" - New-Item -Path $(Build.ArtifactStagingDirectory)/pwshMarinerBuildAmd64 -ItemType Directory - tar -xf $(Build.ArtifactStagingDirectory)/pwshMarinerBuildAmd64-signed/pwshMarinerBuildAmd64.tar.gz -C $(Build.ArtifactStagingDirectory)/pwshMarinerBuildAmd64 - - # for mariner arm64 - Write-Verbose -Verbose "Expanding $(Build.ArtifactStagingDirectory)/pwshMarinerBuildArm64-signed/pwshMarinerBuildArm64.tar.gz to $(Build.ArtifactStagingDirectory)/pwshMarinerBuildArm64" - New-Item -Path $(Build.ArtifactStagingDirectory)/pwshMarinerBuildArm64 -ItemType Directory - tar -xf $(Build.ArtifactStagingDirectory)/pwshMarinerBuildArm64-signed/pwshMarinerBuildArm64.tar.gz -C $(Build.ArtifactStagingDirectory)/pwshMarinerBuildArm64 - } - - if ('$(build)' -eq 'alpine') { - Write-Verbose -Verbose "Expanding $(Build.ArtifactStagingDirectory)/pwshLinuxBuildAlpine-signed/pwshLinuxBuildAlpine.tar.gz to $(Build.ArtifactStagingDirectory)/pwshLinuxBuild" - New-Item -Path $(Build.ArtifactStagingDirectory)/pwshLinuxBuild -ItemType Directory - tar -xf $(Build.ArtifactStagingDirectory)/pwshLinuxBuildAlpine-signed/pwshLinuxBuildAlpine.tar.gz -C $(Build.ArtifactStagingDirectory)/pwshLinuxBuild - - Write-Verbose -Verbose "Expanding $(Build.ArtifactStagingDirectory)/pwshAlpineFxdBuildAmd64-signed/pwshAlpineFxdBuildAmd64.tar.gz to $(Build.ArtifactStagingDirectory)/pwshAlpineFxdBuildAmd64" - New-Item -Path $(Build.ArtifactStagingDirectory)/pwshAlpineFxdBuildAmd64 -ItemType Directory - tar -xf $(Build.ArtifactStagingDirectory)/pwshAlpineFxdBuildAmd64-signed/pwshAlpineFxdBuildAmd64.tar.gz -C $(Build.ArtifactStagingDirectory)/pwshAlpineFxdBuildAmd64 - } - - if ('$(build)' -eq 'fxdependent') { - Write-Verbose -Verbose "Expanding $(Build.ArtifactStagingDirectory)/pwshLinuxBuildFxdependent-signed/pwshLinuxBuildFxdependent.tar.gz to $(Build.ArtifactStagingDirectory)/pwshLinuxBuild" - New-Item -Path $(Build.ArtifactStagingDirectory)/pwshLinuxBuild -ItemType Directory - tar -xf $(Build.ArtifactStagingDirectory)/pwshLinuxBuildFxdependent-signed/pwshLinuxBuildFxdependent.tar.gz -C $(Build.ArtifactStagingDirectory)/pwshLinuxBuild - } - displayName: Expand all signed tar.gz - - - pwsh: | - Get-ChildItem '$(Build.ArtifactStagingDirectory)' | Select-Object -Property 'unixmode', 'size', 'name' - displayName: Capture expanded - - - checkout: self - clean: true - - - checkout: ComplianceRepo - clean: true - - - template: SetVersionVariables.yml - parameters: - ReleaseTagVar: $(ReleaseTagVar) - - - pwsh: | - # create folder - sudo mkdir /PowerShell - - # make the current user the owner - sudo chown $env:USER /PowerShell - displayName: 'Create /PowerShell' - - - template: cloneToOfficialPath.yml - - - template: insert-nuget-config-azfeed.yml - parameters: - repoRoot: $(PowerShellRoot) - - - powershell: | - import-module "$env:POWERSHELLROOT/build.psm1" - Sync-PSTags -AddRemoteIfMissing - displayName: SyncTags - condition: and(succeeded(), ne(variables['SkipBuild'], 'true')) - workingDirectory: $(PowerShellRoot) - - - powershell: | - Import-Module "$env:POWERSHELLROOT/build.psm1" - - Start-PSBootstrap -Package - displayName: 'Bootstrap' - condition: and(succeeded(), ne(variables['SkipBuild'], 'true')) - workingDirectory: $(PowerShellRoot) - env: - __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) - - - powershell: | - try { - Import-Module "$env:POWERSHELLROOT/build.psm1" - Import-Module "$env:POWERSHELLROOT/tools/packaging" - - $metadata = Get-Content "$env:POWERSHELLROOT/tools/metadata.json" -Raw | ConvertFrom-Json - - # LTSRelease.Package indicates that the release should be packaged as an LTS - $LTS = $metadata.LTSRelease.Package - Write-Verbose -Verbose -Message "LTS is set to: $LTS" - - Invoke-AzDevOpsLinuxPackageCreation -ReleaseTag '$(ReleaseTagVar)' -BuildType '$(build)' - - if ($LTS) { - Write-Verbose -Verbose "Packaging LTS" - Invoke-AzDevOpsLinuxPackageCreation -LTS -ReleaseTag '$(ReleaseTagVar)' -BuildType '$(build)' - } - } catch { - Get-Error - throw - } - displayName: 'Package' - condition: and(succeeded(), ne(variables['SkipBuild'], 'true')) - workingDirectory: $(PowerShellRoot) - - - powershell: | - $linuxPackages = Get-ChildItem "$env:POWERSHELLROOT/powershell*" -Include *.deb,*.rpm,*.tar.gz - - $bucket = 'release' - foreach ($linuxPackage in $linuxPackages) - { - $filePath = $linuxPackage.FullName - Write-Verbose "Publishing $filePath to $bucket" -Verbose - Write-Host "##vso[artifact.upload containerfolder=$bucket;artifactname=$bucket]$filePath" - } - displayName: Publish artifacts - condition: and(succeeded(), ne(variables['SkipBuild'], 'true')) - workingDirectory: $(PowerShellRoot) - retryCountOnTaskFailure: 2 - - - - template: /tools/releaseBuild/azureDevOps/templates/step/finalize.yml - -- job: upload_${{ parameters.buildName }} - displayName: ${{ parameters.uploadDisplayName }} ${{ parameters.buildName }} - dependsOn: pkg_${{ parameters.buildName }} - condition: succeeded() - pool: - name: PowerShell1ES - demands: - - ImageOverride -equals PSMMS2019-Secure - variables: - - name: buildName - value: ${{ parameters.buildName }} - - group: ESRP - - name: runCodesignValidationInjection - value: false - - name: NugetSecurityAnalysisWarningLevel - value: none - - name: skipComponentGovernanceDetection - value: true - - steps: - - checkout: self - clean: true - - - checkout: ComplianceRepo - clean: true - - - template: SetVersionVariables.yml - parameters: - ReleaseTagVar: $(ReleaseTagVar) - - template: shouldSign.yml - - - task: DownloadBuildArtifacts@0 - displayName: 'Download Deb Artifacts' - inputs: - downloadType: specific - itemPattern: '**/*.deb' - downloadPath: '$(System.ArtifactsDirectory)\finished' - condition: and(eq(variables['buildName'], 'DEB'), succeeded()) - - - task: DownloadBuildArtifacts@0 - displayName: 'Download tar.gz Artifacts copy' - inputs: - downloadType: specific - itemPattern: '**/*.tar.gz' - downloadPath: '$(System.ArtifactsDirectory)\finished' - - - powershell: | - Write-Host 'We handle the min-size package only when uploading for deb build.' - Write-Host '- For deb build, the min-size package is moved to a separate folder "finished\minSize",' - Write-Host ' so that the min-size package can be uploaded to a different Az Blob container.' - Write-Host '- For other builds, the min-size package is removed after being downloaded, so that it' - Write-Host ' does not get accidentally uploaded to the wrong Az Blob container.' - - $minSizePkg = '$(System.ArtifactsDirectory)\finished\release\*-gc.tar.gz' - if (Test-Path -Path $minSizePkg) - { - if ('$(buildName)' -eq 'DEB') - { - $minSizeDir = '$(System.ArtifactsDirectory)\finished\minSize' - New-Item -Path $minSizeDir -Type Directory -Force > $null - Move-Item -Path $minSizePkg -Destination $minSizeDir - - Write-Host "`nCapture the min-size package moved to the target folder." - Get-ChildItem -Path $minSizeDir - } - else - { - Write-Host '$(buildName): Remove the min-size package.' - Remove-Item -Path $minSizePkg -Force - } - } - else - { - Write-Host 'min-size package not found, so skip this step.' - } - displayName: 'Move minSize package to separate folder' - - - task: DownloadBuildArtifacts@0 - displayName: 'Download rpm Artifacts copy' - inputs: - downloadType: specific - itemPattern: '**/*.rpm' - downloadPath: '$(System.ArtifactsDirectory)\rpm' - condition: and(eq(variables['buildName'], 'RPM'), succeeded()) - - - template: EsrpScan.yml@ComplianceRepo - parameters: - scanPath: $(System.ArtifactsDirectory) - pattern: | - **\*.rpm - **\*.deb - **\*.tar.gz - - - ${{ if eq(variables['buildName'], 'RPM') }}: - - template: EsrpSign.yml@ComplianceRepo - parameters: - buildOutputPath: $(System.ArtifactsDirectory)\rpm - signOutputPath: $(Build.StagingDirectory)\signedPackages - certificateId: "CP-450779-Pgp" - pattern: | - **\*.rh.*.rpm - useMinimatch: true - shouldSign: $(SHOULD_SIGN) - displayName: Sign RedHat RPM - OutputMode: AlwaysCopy - - - ${{ if eq(variables['buildName'], 'RPM') }}: - - template: EsrpSign.yml@ComplianceRepo - parameters: - # Sign in-place, previous task copied the files to this folder - buildOutputPath: $(Build.StagingDirectory)\signedPackages - signOutputPath: $(Build.StagingDirectory)\signedPackages - certificateId: "CP-459159-Pgp" - pattern: | - **\*.cm.*.rpm - **\*.cm?.*.rpm - useMinimatch: true - shouldSign: $(SHOULD_SIGN) - displayName: Sign Mariner RPM - OutputMode: NeverCopy - - # requires windows - - ${{ if ne(variables['buildName'], 'RPM') }}: - - task: AzureFileCopy@4 - displayName: 'Upload to Azure - DEB and tar.gz' - inputs: - SourcePath: '$(System.ArtifactsDirectory)\finished\release\*' - azureSubscription: '$(AzureFileCopySubscription)' - Destination: AzureBlob - storage: '$(StorageAccount)' - ContainerName: '$(AzureVersion)' - retryCountOnTaskFailure: 2 - - - template: upload-final-results.yml - parameters: - artifactPath: $(System.ArtifactsDirectory)\finished\release - - # requires windows - - task: AzureFileCopy@4 - displayName: 'Upload to Azure - min-size package for Guest Config' - inputs: - SourcePath: '$(System.ArtifactsDirectory)\finished\minSize\*' - azureSubscription: '$(AzureFileCopySubscription)' - Destination: AzureBlob - storage: '$(StorageAccount)' - ContainerName: '$(AzureVersion)-gc' - condition: and(eq(variables['buildName'], 'DEB'), succeeded()) - retryCountOnTaskFailure: 2 - - - template: upload-final-results.yml - parameters: - artifactPath: $(System.ArtifactsDirectory)\finished\minSize - condition: and(eq(variables['buildName'], 'DEB'), succeeded()) - - # requires windows - - task: AzureFileCopy@4 - displayName: 'Upload to Azure - RPM - Unsigned' - inputs: - SourcePath: '$(System.ArtifactsDirectory)\rpm\release\*' - azureSubscription: '$(AzureFileCopySubscription)' - Destination: AzureBlob - storage: '$(StorageAccount)' - ContainerName: '$(AzureVersion)' - condition: and(and(succeeded(), ne(variables['SHOULD_SIGN'], 'true')),eq(variables['buildName'], 'RPM')) - retryCountOnTaskFailure: 2 - - # requires windows - - task: AzureFileCopy@4 - displayName: 'Upload to Azure - RPM - Signed' - inputs: - SourcePath: '$(Build.StagingDirectory)\signedPackages\release\*' - azureSubscription: '$(AzureFileCopySubscription)' - Destination: AzureBlob - storage: '$(StorageAccount)' - ContainerName: '$(AzureVersion)' - condition: and(and(succeeded(), eq(variables['SHOULD_SIGN'], 'true')),eq(variables['buildName'], 'RPM')) - retryCountOnTaskFailure: 2 - - - template: upload-final-results.yml - parameters: - artifactPath: $(System.ArtifactsDirectory)\rpm\release - condition: and(and(succeeded(), ne(variables['SHOULD_SIGN'], 'true')),eq(variables['buildName'], 'RPM')) - - - template: upload-final-results.yml - parameters: - artifactPath: '$(Build.StagingDirectory)\signedPackages\release' - condition: and(and(succeeded(), eq(variables['SHOULD_SIGN'], 'true')),eq(variables['buildName'], 'RPM')) - - - template: /tools/releaseBuild/azureDevOps/templates/step/finalize.yml diff --git a/tools/releaseBuild/azureDevOps/templates/linux.yml b/tools/releaseBuild/azureDevOps/templates/linux.yml deleted file mode 100644 index bb343bed54e..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/linux.yml +++ /dev/null @@ -1,313 +0,0 @@ -parameters: - buildName: '' - uploadDisplayName: 'Upload' - parentJob: '' - -jobs: -- job: build_${{ parameters.buildName }} - displayName: Build ${{ parameters.buildName }} - condition: succeeded() - pool: - name: PowerShell1ES - demands: - - ImageOverride -equals PSMMSUbuntu20.04-Secure - dependsOn: ${{ parameters.parentJob }} - variables: - - name: runCodesignValidationInjection - value: false - - name: build - value: ${{ parameters.buildName }} - - name: NugetSecurityAnalysisWarningLevel - value: none - - group: ESRP - - group: DotNetPrivateBuildAccess - - steps: - - checkout: self - clean: true - - - checkout: ComplianceRepo - clean: true - - - template: SetVersionVariables.yml - parameters: - ReleaseTagVar: $(ReleaseTagVar) - - - pwsh: | - # create folder - sudo mkdir /PowerShell - - # make the current user the owner - sudo chown $env:USER /PowerShell - displayName: 'Create /PowerShell' - - - template: cloneToOfficialPath.yml - - - template: insert-nuget-config-azfeed.yml - parameters: - repoRoot: $(PowerShellRoot) - - - powershell: | - import-module "$env:POWERSHELLROOT/build.psm1" - Sync-PSTags -AddRemoteIfMissing - displayName: SyncTags - condition: and(succeeded(), ne(variables['SkipBuild'], 'true')) - workingDirectory: $(PowerShellRoot) - - - powershell: | - Import-Module "$env:POWERSHELLROOT/build.psm1" - - Start-PSBootstrap -Package - displayName: 'Bootstrap' - condition: and(succeeded(), ne(variables['SkipBuild'], 'true')) - workingDirectory: $(PowerShellRoot) - env: - __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) - - - pwsh: | - try { - Import-Module "$env:POWERSHELLROOT/build.psm1" - Import-Module "$env:POWERSHELLROOT/tools/packaging" - - Invoke-AzDevOpsLinuxPackageBuild -ReleaseTag '$(ReleaseTagVar)' -BuildType '$(build)' - - Write-Verbose -Verbose "File permisions after building" - Get-ChildItem -Path $(System.ArtifactsDirectory)/pwshLinuxBuild/pwsh | Select-Object -Property 'unixmode', 'size', 'name' - - } catch { - Get-Error - throw - } - displayName: 'Build' - condition: and(succeeded(), ne(variables['SkipBuild'], 'true')) - workingDirectory: $(PowerShellRoot) - - - template: Sbom.yml@ComplianceRepo - parameters: - BuildDropPath: '$(System.ArtifactsDirectory)/pwshLinuxBuild' - Build_Repository_Uri: $(Github_Build_Repository_Uri) - displayName: ${{ parameters.buildName }} SBOM - PackageName: PowerShell Linux - PackageVersion: $(Version) - sourceScanPath: '$(PowerShellRoot)/tools' - - - ${{ if eq(variables.build,'rpm') }} : - - template: Sbom.yml@ComplianceRepo - parameters: - BuildDropPath: '$(System.ArtifactsDirectory)/pwshMarinerBuildAmd64' - Build_Repository_Uri: $(Github_Build_Repository_Uri) - displayName: Mariner x64 SBOM - PackageName: PowerShell Linux Framework Dependent - PackageVersion: $(Version) - sourceScanPath: '$(PowerShellRoot)/tools' - - - ${{ if eq(variables.build,'rpm') }} : - - template: Sbom.yml@ComplianceRepo - parameters: - BuildDropPath: '$(System.ArtifactsDirectory)/pwshMarinerBuildArm64' - Build_Repository_Uri: $(Github_Build_Repository_Uri) - displayName: Mariner arm64 SBOM - PackageName: PowerShell Linux Framework Dependent - PackageVersion: $(Version) - sourceScanPath: '$(PowerShellRoot)/tools' - - - ${{ if eq(variables.build,'deb') }} : - - template: Sbom.yml@ComplianceRepo - parameters: - BuildDropPath: '$(System.ArtifactsDirectory)/pwshLinuxBuildMinSize' - Build_Repository_Uri: $(Github_Build_Repository_Uri) - displayName: MinSize SBOM - PackageName: PowerShell Linux Minimum Size - PackageVersion: $(Version) - sourceScanPath: '$(PowerShellRoot)/tools' - - - ${{ if eq(variables.build,'deb') }} : - - template: Sbom.yml@ComplianceRepo - parameters: - BuildDropPath: '$(System.ArtifactsDirectory)/pwshLinuxBuildArm32' - Build_Repository_Uri: $(Github_Build_Repository_Uri) - displayName: Arm32 SBOM - PackageName: PowerShell Linux Arm32 - PackageVersion: $(Version) - sourceScanPath: '$(PowerShellRoot)/tools' - - - ${{ if eq(variables.build,'deb') }} : - - template: Sbom.yml@ComplianceRepo - parameters: - BuildDropPath: '$(System.ArtifactsDirectory)/pwshLinuxBuildArm64' - Build_Repository_Uri: $(Github_Build_Repository_Uri) - displayName: Arm64 SBOM - PackageName: PowerShell Linux Arm64 - PackageVersion: $(Version) - sourceScanPath: '$(PowerShellRoot)/tools' - - - ${{ if eq(variables.build,'alpine') }} : - - template: Sbom.yml@ComplianceRepo - parameters: - BuildDropPath: '$(System.ArtifactsDirectory)/pwshAlpineFxdBuildAmd64' - Build_Repository_Uri: $(Github_Build_Repository_Uri) - displayName: Alpine FXD SBOM - PackageName: PowerShell Alpine Framework Dependent AMD64 - PackageVersion: $(Version) - sourceScanPath: '$(PowerShellRoot)/tools' - - - pwsh: | - Set-Location '$(System.ArtifactsDirectory)/pwshLinuxBuild' - Write-Verbose -Verbose "File permisions before compressing" - Get-ChildItem -Path $(Build.ArtifactStagingDirectory)/pwshLinuxBuild/pwsh | Select-Object -Property 'unixmode', 'size', 'name' - tar -czvf $(System.ArtifactsDirectory)/pwshLinuxBuild.tar.gz * - displayName: Compress pwshLinuxBuild - - - ${{ if eq(variables.build,'deb') }} : - - pwsh: | - Set-Location '$(System.ArtifactsDirectory)/pwshLinuxBuildMinSize' - tar -czvf $(System.ArtifactsDirectory)/pwshLinuxBuildMinSize.tar.gz * - Set-Location '$(System.ArtifactsDirectory)/pwshLinuxBuildArm32' - tar -czvf $(System.ArtifactsDirectory)/pwshLinuxBuildArm32.tar.gz * - Set-Location '$(System.ArtifactsDirectory)/pwshLinuxBuildArm64' - tar -czvf $(System.ArtifactsDirectory)/pwshLinuxBuildArm64.tar.gz * - displayName: Compress deb - - - ${{ if eq(variables.build,'rpm') }} : - - pwsh: | - Set-Location '$(System.ArtifactsDirectory)/pwshMarinerBuildAmd64' - tar -czvf $(System.ArtifactsDirectory)/pwshMarinerBuildAmd64.tar.gz * - displayName: Compress pwshMarinerBuildAmd64 - - - ${{ if eq(variables.build,'alpine') }} : - - pwsh: | - Set-Location '$(System.ArtifactsDirectory)/pwshAlpineFxdBuildAmd64' - tar -czvf $(System.ArtifactsDirectory)/pwshAlpineFxdBuildAmd64.tar.gz * - displayName: Compress pwshAlpineFxdBuildAmd64 - - - ${{ if eq(variables.build,'rpm') }} : - - pwsh: | - Set-Location '$(System.ArtifactsDirectory)/pwshMarinerBuildArm64' - tar -czvf $(System.ArtifactsDirectory)/pwshMarinerBuildArm64.tar.gz * - displayName: Compress pwshMarinerBuildArm64 - - - ${{ if eq(variables.build,'deb') }} : - - task: PublishPipelineArtifact@1 - inputs: - path: '$(System.ArtifactsDirectory)/pwshLinuxBuild.tar.gz' - artifactName: pwshLinuxBuild.tar.gz - retryCountOnTaskFailure: 2 - - - ${{ if eq(variables.build,'deb') }} : - - task: PublishPipelineArtifact@1 - inputs: - path: '$(System.ArtifactsDirectory)/pwshLinuxBuild-meta' - artifactName: pwshLinuxBuild-meta - retryCountOnTaskFailure: 2 - - - ${{ if eq(variables.build,'deb') }} : - - task: PublishPipelineArtifact@1 - inputs: - path: '$(System.ArtifactsDirectory)/pwshLinuxBuildMinSize.tar.gz' - artifactName: pwshLinuxBuildMinSize.tar.gz - retryCountOnTaskFailure: 2 - - - ${{ if eq(variables.build,'deb') }} : - - task: PublishPipelineArtifact@1 - inputs: - path: '$(System.ArtifactsDirectory)/pwshLinuxBuildMinSize-meta' - artifactName: pwshLinuxBuildMinSize-meta - retryCountOnTaskFailure: 2 - - - ${{ if eq(variables.build,'deb') }} : - - task: PublishPipelineArtifact@1 - inputs: - path: '$(System.ArtifactsDirectory)/pwshLinuxBuildArm32.tar.gz' - artifactName: pwshLinuxBuildArm32.tar.gz - retryCountOnTaskFailure: 2 - - - ${{ if eq(variables.build,'deb') }} : - - task: PublishPipelineArtifact@1 - inputs: - path: '$(System.ArtifactsDirectory)/pwshLinuxBuildArm32-meta' - artifactName: pwshLinuxBuildArm32-meta - retryCountOnTaskFailure: 2 - - - ${{ if eq(variables.build,'deb') }} : - - task: PublishPipelineArtifact@1 - inputs: - path: '$(System.ArtifactsDirectory)/pwshLinuxBuildArm64.tar.gz' - artifactName: pwshLinuxBuildArm64.tar.gz - retryCountOnTaskFailure: 2 - - - ${{ if eq(variables.build,'deb') }} : - - task: PublishPipelineArtifact@1 - inputs: - path: '$(System.ArtifactsDirectory)/pwshLinuxBuildArm64-meta' - artifactName: pwshLinuxBuildArm64-meta - retryCountOnTaskFailure: 2 - - - ${{ if eq(variables.build,'rpm') }} : - - task: PublishPipelineArtifact@1 - inputs: - path: '$(System.ArtifactsDirectory)/pwshMarinerBuildAmd64.tar.gz' - artifactName: pwshMarinerBuildAmd64.tar.gz - retryCountOnTaskFailure: 2 - - - ${{ if eq(variables.build,'rpm') }} : - - task: PublishPipelineArtifact@1 - inputs: - path: '$(System.ArtifactsDirectory)/pwshMarinerBuildAmd64-meta' - artifactName: pwshMarinerBuildAmd64-meta - retryCountOnTaskFailure: 2 - - - ${{ if eq(variables.build,'rpm') }} : - - task: PublishPipelineArtifact@1 - inputs: - path: '$(System.ArtifactsDirectory)/pwshMarinerBuildArm64.tar.gz' - artifactName: pwshMarinerBuildArm64.tar.gz - retryCountOnTaskFailure: 2 - - - ${{ if eq(variables.build,'rpm') }} : - - task: PublishPipelineArtifact@1 - inputs: - path: '$(System.ArtifactsDirectory)/pwshMarinerBuildArm64-meta' - artifactName: pwshMarinerBuildArm64-meta - retryCountOnTaskFailure: 2 - - - ${{ if eq(variables.build,'alpine') }} : - - task: PublishPipelineArtifact@1 - inputs: - path: '$(System.ArtifactsDirectory)/pwshLinuxBuild.tar.gz' - artifactName: pwshLinuxBuildAlpine.tar.gz - retryCountOnTaskFailure: 2 - - - ${{ if eq(variables.build,'alpine') }} : - - task: PublishPipelineArtifact@1 - inputs: - path: '$(System.ArtifactsDirectory)/pwshLinuxBuild-meta' - artifactName: pwshLinuxBuildAlpine-meta - retryCountOnTaskFailure: 2 - - - ${{ if eq(variables.build,'alpine') }} : - - task: PublishPipelineArtifact@1 - inputs: - path: '$(System.ArtifactsDirectory)/pwshAlpineFxdBuildAmd64.tar.gz' - artifactName: pwshAlpineFxdBuildAmd64.tar.gz - retryCountOnTaskFailure: 2 - - - ${{ if eq(variables.build,'alpine') }} : - - task: PublishPipelineArtifact@1 - inputs: - path: '$(System.ArtifactsDirectory)/pwshAlpineFxdBuildAmd64-meta' - artifactName: pwshAlpineFxdBuildAmd64-meta - retryCountOnTaskFailure: 2 - - - ${{ if eq(variables.build,'fxdependent') }} : - - task: PublishPipelineArtifact@1 - inputs: - path: '$(System.ArtifactsDirectory)/pwshLinuxBuild.tar.gz' - artifactName: pwshLinuxBuildFxdependent.tar.gz - retryCountOnTaskFailure: 2 - - - ${{ if eq(variables.build,'fxdependent') }} : - - task: PublishPipelineArtifact@1 - inputs: - path: '$(System.ArtifactsDirectory)/pwshLinuxBuild-meta' - artifactName: pwshLinuxBuildFxdependent-meta - retryCountOnTaskFailure: 2 diff --git a/tools/releaseBuild/azureDevOps/templates/mac-file-signing.yml b/tools/releaseBuild/azureDevOps/templates/mac-file-signing.yml deleted file mode 100644 index 8159c2bc7d9..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/mac-file-signing.yml +++ /dev/null @@ -1,121 +0,0 @@ -parameters: - buildArchitecture: 'x64' - -jobs: - - job: MacFileSigningJob_${{ parameters.buildArchitecture }} - displayName: macOS File signing ${{ parameters.buildArchitecture }} - condition: succeeded() - pool: - name: PowerShell1ES - demands: - - ImageOverride -equals PSMMS2019-Secure - - variables: - - group: ESRP - - name: runCodesignValidationInjection - value: false - - name: NugetSecurityAnalysisWarningLevel - value: none - - name: repoFolder - value: PowerShell - - name: repoRoot - value: $(Agent.BuildDirectory)\$(repoFolder) - - name: complianceRepoFolder - value: compliance - - steps: - - checkout: self - clean: true - path: $(repoFolder) - - - checkout: ComplianceRepo - clean: true - path: $(complianceRepoFolder) - - - template: SetVersionVariables.yml - parameters: - ReleaseTagVar: $(ReleaseTagVar) - - - template: shouldSign.yml - - - task: DownloadBuildArtifacts@0 - inputs: - artifactName: 'macosBinResults' - itemPattern: '**/*.zip' - downloadPath: '$(System.ArtifactsDirectory)\Symbols' - - - pwsh: | - Get-ChildItem "$(System.ArtifactsDirectory)\*" -Recurse - displayName: 'Capture Downloaded Artifacts' - # Diagnostics is not critical it passes every time it runs - continueOnError: true - - - pwsh: | - $zipPath = Get-Item '$(System.ArtifactsDirectory)\Symbols\macosBinResults\*symbol*${{ parameters.buildArchitecture }}*.zip' - Write-Verbose -Verbose "Zip Path: $zipPath" - - $expandedFolder = $zipPath.BaseName - Write-Host "sending.. vso[task.setvariable variable=SymbolsFolder]$expandedFolder" - Write-Host "##vso[task.setvariable variable=SymbolsFolder]$expandedFolder" - - Expand-Archive -Path $zipPath -Destination "$(System.ArtifactsDirectory)\$expandedFolder" -Force - displayName: Expand symbols zip - - - pwsh: | - Get-ChildItem "$(System.ArtifactsDirectory)\*" -Recurse - displayName: 'Capture artifacts dir Binaries' - - - pwsh: | - Get-ChildItem "$(System.ArtifactsDirectory)\$(SymbolsFolder)" -Recurse -Include pwsh, *.dylib - displayName: 'Capture Expanded Binaries' - # Diagnostics is not critical it passes every time it runs - continueOnError: true - - - pwsh: | - $null = new-item -type directory -path "$(Build.StagingDirectory)\macos" - $zipFile = "$(Build.StagingDirectory)\macos\powershell-files-$(Version)-osx-${{ parameters.buildArchitecture }}.zip" - Get-ChildItem "$(System.ArtifactsDirectory)\$(SymbolsFolder)" -Recurse -Include pwsh, *.dylib | - Compress-Archive -Destination $zipFile - Write-Host $zipFile - displayName: 'Compress macOS binary files' - - - template: EsrpSign.yml@ComplianceRepo - parameters: - buildOutputPath: $(Build.StagingDirectory)\macos - signOutputPath: $(Build.StagingDirectory)\signedMacOSPackages - certificateId: "CP-401337-Apple" - pattern: | - **\*.zip - useMinimatch: true - shouldSign: $(SHOULD_SIGN) - displayName: Sign macOS Binaries - - - pwsh: | - $destination = "$(System.ArtifactsDirectory)\azureMacOs_${{ parameters.buildArchitecture }}" - New-Item -Path $destination -Type Directory - $zipPath = Get-ChildItem "$(Build.StagingDirectory)\signedMacOSPackages\powershell-*.zip" -Recurse | select-object -expandproperty fullname - foreach ($z in $zipPath) { Expand-Archive -Path $z -DestinationPath $destination } - displayName: 'Extract and copy macOS artifacts for upload' - condition: and(succeeded(), eq(variables['SHOULD_SIGN'], 'true')) - - - template: upload-final-results.yml - parameters: - artifactPath: $(System.ArtifactsDirectory)\azureMacOs_${{ parameters.buildArchitecture }} - artifactFilter: "*" - artifactName: signedMacOsBins_${{ parameters.buildArchitecture }} - condition: and(succeeded(), eq(variables['SHOULD_SIGN'], 'true')) - - - ${{ if eq(variables['SHOULD_SIGN'], 'true') }}: - - template: EsrpScan.yml@ComplianceRepo - parameters: - scanPath: $(System.ArtifactsDirectory)\azureMacOs_${{ parameters.buildArchitecture }} - pattern: | - **\* - - - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 - displayName: 'Component Detection' - inputs: - sourceScanPath: '$(repoRoot)\tools' - snapshotForceEnabled: true - - - template: /tools/releaseBuild/azureDevOps/templates/step/finalize.yml diff --git a/tools/releaseBuild/azureDevOps/templates/mac-package-build.yml b/tools/releaseBuild/azureDevOps/templates/mac-package-build.yml deleted file mode 100644 index c853a21ef37..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/mac-package-build.yml +++ /dev/null @@ -1,143 +0,0 @@ -parameters: - parentJob: '' - buildArchitecture: x64 - -jobs: -- job: package_macOS_${{ parameters.buildArchitecture }} - displayName: Package macOS ${{ parameters.buildArchitecture }} - condition: succeeded() - pool: - vmImage: macos-latest - variables: - # Turn off Homebrew analytics - - name: HOMEBREW_NO_ANALYTICS - value: 1 - - name: runCodesignValidationInjection - value: false - - name: NugetSecurityAnalysisWarningLevel - value: none - - group: DotNetPrivateBuildAccess - steps: - - checkout: self - clean: true - - - pwsh: | - # create folder - sudo mkdir "$(Agent.TempDirectory)/PowerShell" - - # make the current user the owner - sudo chown $env:USER "$(Agent.TempDirectory)/PowerShell" - displayName: 'Create $(Agent.TempDirectory)/PowerShell' - - - template: SetVersionVariables.yml - parameters: - ReleaseTagVar: $(ReleaseTagVar) - - - template: shouldSign.yml - - - template: cloneToOfficialPath.yml - parameters: - nativePathRoot: '$(Agent.TempDirectory)' - - - task: DownloadBuildArtifacts@0 - displayName: Download macosBinResults - inputs: - artifactName: 'macosBinResults' - itemPattern: '**/*${{ parameters.buildArchitecture }}.zip' - downloadPath: '$(System.ArtifactsDirectory)/Symbols' - - - task: DownloadBuildArtifacts@0 - displayName: Download signedMacOsBins - inputs: - artifactName: 'signedMacOsBins_${{ parameters.buildArchitecture }}' - itemPattern: '**/*' - downloadPath: '$(System.ArtifactsDirectory)/macOsBins' - condition: and(succeeded(), eq(variables['SHOULD_SIGN'], 'true')) - - - pwsh: | - Get-ChildItem "$(System.ArtifactsDirectory)\*" -Recurse - displayName: 'Capture Downloaded Artifacts' - # Diagnostics is not critical it passes every time it runs - continueOnError: true - - - pwsh: | - $zipPath = Get-Item '$(System.ArtifactsDirectory)\Symbols\macosBinResults\*symbol*${{ parameters.buildArchitecture }}.zip' - Write-Verbose -Verbose "Zip Path: $zipPath" - - $expandedFolder = $zipPath.BaseName - Write-Host "sending.. vso[task.setvariable variable=SymbolsFolder]$expandedFolder" - Write-Host "##vso[task.setvariable variable=SymbolsFolder]$expandedFolder" - - Expand-Archive -Path $zipPath -Destination "$(System.ArtifactsDirectory)\$expandedFolder" -Force - displayName: Expand symbols zip - - - pwsh: | - Import-Module $(PowerShellRoot)/build.psm1 -Force - Import-Module $(PowerShellRoot)/tools/packaging -Force - $signedFilesPath = '$(System.ArtifactsDirectory)/macOsBins/signedMacOsBins_${{ parameters.buildArchitecture }}/' - $BuildPath = '$(System.ArtifactsDirectory)\$(SymbolsFolder)' - - Update-PSSignedBuildFolder -BuildPath $BuildPath -SignedFilesPath $SignedFilesPath - displayName: Merge signed files with Build - condition: and(succeeded(), eq(variables['SHOULD_SIGN'], 'true')) - - - template: Sbom.yml@ComplianceRepo - parameters: - BuildDropPath: '$(System.ArtifactsDirectory)/$(SymbolsFolder)' - Build_Repository_Uri: $(Github_Build_Repository_Uri) - PackageName: PowerShell macOS ${{ parameters.buildArchitecture }} - PackageVersion: $(Version) - sourceScanPath: '$(PowerShellRoot)/tools' - - - pwsh: | - Import-Module $(PowerShellRoot)/build.psm1 -Force - Import-Module $(PowerShellRoot)/tools/packaging -Force - - $destFolder = '$(System.ArtifactsDirectory)\signedZip' - $BuildPath = '$(System.ArtifactsDirectory)\$(SymbolsFolder)' - - $null = New-Item -ItemType Directory -Path $destFolder -Force - - $BuildPackagePath = New-PSBuildZip -BuildPath $BuildPath -DestinationFolder $destFolder - - Write-Verbose -Verbose "New-PSSignedBuildZip returned `$BuildPackagePath as: $BuildPackagePath" - Write-Host "##vso[artifact.upload containerfolder=results;artifactname=results]$BuildPackagePath" - - $vstsCommandString = "vso[task.setvariable variable=BuildPackagePath]$BuildPackagePath" - Write-Host ("sending " + $vstsCommandString) - Write-Host "##$vstsCommandString" - displayName: Compress signed files - retryCountOnTaskFailure: 2 - - - - pwsh: | - try { - tools/releaseBuild/macOS/PowerShellPackageVsts.ps1 -location $(PowerShellRoot) -BootStrap - } catch { - Get-Error - throw - } - displayName: 'Bootstrap VM' - env: - __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) - - - pwsh: | - # Add -SkipReleaseChecks as a mitigation to unblock release. - # macos-10.15 does not allow creating a folder under root. Hence, moving the folder. - try { - $(Build.SourcesDirectory)/tools/releaseBuild/macOS/PowerShellPackageVsts.ps1 -ReleaseTag $(ReleaseTagVar) -Destination $(System.ArtifactsDirectory) -location $(PowerShellRoot) -ArtifactName macosPkgResults -BuildZip $(BuildPackagePath) -ExtraPackage "tar" -Runtime 'osx-${{ parameters.buildArchitecture }}' -SkipReleaseChecks - } catch { - Get-Error - throw - } - displayName: 'Package' - env: - __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) - - - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 - displayName: 'Component Detection' - inputs: - sourceScanPath: '$(PowerShellRoot)/tools' - snapshotForceEnabled: true - - - template: /tools/releaseBuild/azureDevOps/templates/step/finalize.yml diff --git a/tools/releaseBuild/azureDevOps/templates/mac-package-signing.yml b/tools/releaseBuild/azureDevOps/templates/mac-package-signing.yml deleted file mode 100644 index d4901580b0b..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/mac-package-signing.yml +++ /dev/null @@ -1,135 +0,0 @@ -parameters: - buildArchitecture: x64 - -jobs: -- job: MacPackageSigningJob_${{ parameters.buildArchitecture }} - displayName: macOS Package signing ${{ parameters.buildArchitecture }} - condition: succeeded() - pool: - name: PowerShell1ES - demands: - - ImageOverride -equals PSMMS2019-Secure - variables: - - group: ESRP - - name: runCodesignValidationInjection - value: false - - name: NugetSecurityAnalysisWarningLevel - value: none - - name: repoFolder - value: PowerShell - - name: repoRoot - value: $(Agent.BuildDirectory)\$(repoFolder) - - name: complianceRepoFolder - value: compliance - - steps: - - checkout: self - clean: true - path: $(repoFolder) - - - checkout: ComplianceRepo - clean: true - path: $(complianceRepoFolder) - - - template: shouldSign.yml - - - template: SetVersionVariables.yml - parameters: - ReleaseTagVar: $(ReleaseTagVar) - - - task: DownloadBuildArtifacts@0 - inputs: - artifactName: 'macosPkgResults' - itemPattern: '**/*' - downloadPath: '$(System.ArtifactsDirectory)' - - - pwsh: | - dir "$(System.ArtifactsDirectory)\*" -Recurse - displayName: 'Capture Downloaded Artifacts' - # Diagnostics is not critical it passes every time it runs - continueOnError: true - - - pwsh: | - $null = new-item -type directory -path "$(Build.StagingDirectory)\macos" - $zipFile = "$(Build.StagingDirectory)\macos\powershell-$(Version)-osx-${{ parameters.buildArchitecture }}.zip" - Compress-Archive -Path "$(System.ArtifactsDirectory)\macosPkgResults\powershell-$(Version)-osx-${{ parameters.buildArchitecture }}.pkg" -Destination $zipFile - Write-Host $zipFile - - $ltsPkgPath = "$(System.ArtifactsDirectory)\macosPkgResults\powershell-lts-$(Version)-osx-${{ parameters.buildArchitecture }}.pkg" - - if(Test-Path $ltsPkgPath) - { - $ltsZipFile = "$(Build.StagingDirectory)\macos\powershell-lts-$(Version)-osx-${{ parameters.buildArchitecture }}.zip" - Compress-Archive -Path $ltsPkgPath -Destination $ltsZipFile - Write-Host $ltsZipFile - } - displayName: 'Compress macOS Package' - - - template: EsrpSign.yml@ComplianceRepo - parameters: - buildOutputPath: $(Build.StagingDirectory)\macos - signOutputPath: $(Build.StagingDirectory)\signedMacOSPackages - certificateId: "CP-401337-Apple" - pattern: | - **\*.zip - useMinimatch: true - shouldSign: $(SHOULD_SIGN) - displayName: Sign pkg - - - template: upload-final-results.yml - parameters: - artifactPath: $(System.ArtifactsDirectory)\macosPkgResults - artifactFilter: "*${{ parameters.buildArchitecture }}.tar.gz" - - - pwsh: | - $destination = "$(System.ArtifactsDirectory)\azureMacOs" - New-Item -Path $destination -Type Directory - $zipPath = dir "$(Build.StagingDirectory)\signedMacOSPackages\powershell-*.zip" -Recurse | select-object -expandproperty fullname - foreach ($z in $zipPath) { Expand-Archive -Path $z -DestinationPath $destination } - $targzPath = dir "$(System.ArtifactsDirectory)\*osx*.tar.gz" -Recurse | select-object -expandproperty fullname - Copy-Item -Path $targzPath -Destination $destination - displayName: 'Extract and copy macOS artifacts for upload' - condition: and(succeeded(), eq(variables['SHOULD_SIGN'], 'true')) - - - template: upload-final-results.yml - parameters: - artifactPath: $(System.ArtifactsDirectory)\azureMacOs - artifactFilter: "*.pkg" - condition: and(succeeded(), eq(variables['SHOULD_SIGN'], 'true')) - - - pwsh: | - $null = new-item -type directory -path "$(Build.StagingDirectory)\macos-unsigned" - Copy-Item -Path "$(System.ArtifactsDirectory)\macosPkgResults\powershell-$(Version)-osx-x64.pkg" -Destination "$(Build.StagingDirectory)\macos-unsigned" - Copy-Item -Path "$(System.ArtifactsDirectory)\macosPkgResults\powershell-$(Version)-osx-x64.tar.gz" -Destination "$(Build.StagingDirectory)\macos-unsigned" - displayName: 'Create unsigned folder to upload' - condition: and(succeeded(), ne(variables['SHOULD_SIGN'], 'true')) - - - task: AzureFileCopy@4 - displayName: 'AzureBlob File Copy - unsigned' - inputs: - SourcePath: '$(Build.StagingDirectory)\macos-unsigned\*' - azureSubscription: '$(AzureFileCopySubscription)' - Destination: AzureBlob - storage: '$(StorageAccount)' - ContainerName: '$(AzureVersion)' - condition: and(succeeded(), ne(variables['SHOULD_SIGN'], 'true')) - retryCountOnTaskFailure: 2 - - - task: AzureFileCopy@4 - displayName: 'AzureBlob File Copy - signed' - inputs: - SourcePath: '$(System.ArtifactsDirectory)\azureMacOs\*' - azureSubscription: '$(AzureFileCopySubscription)' - Destination: AzureBlob - storage: '$(StorageAccount)' - ContainerName: '$(AzureVersion)' - condition: and(succeeded(), eq(variables['SHOULD_SIGN'], 'true')) - retryCountOnTaskFailure: 2 - - - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 - displayName: 'Component Detection' - inputs: - sourceScanPath: '$(repoRoot)/tools' - snapshotForceEnabled: true - - - template: /tools/releaseBuild/azureDevOps/templates/step/finalize.yml diff --git a/tools/releaseBuild/azureDevOps/templates/mac.yml b/tools/releaseBuild/azureDevOps/templates/mac.yml deleted file mode 100644 index d173e900434..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/mac.yml +++ /dev/null @@ -1,68 +0,0 @@ -parameters: - buildArchitecture: 'x64' - -jobs: -- job: build_macOS_${{ parameters.buildArchitecture }} - displayName: Build macOS ${{ parameters.buildArchitecture }} - condition: succeeded() - pool: - vmImage: macos-latest - variables: - # Turn off Homebrew analytics - - name: HOMEBREW_NO_ANALYTICS - value: 1 - - name: runCodesignValidationInjection - value: false - - name: NugetSecurityAnalysisWarningLevel - value: none - - group: DotNetPrivateBuildAccess - steps: - #- task: <task type name>@<version> - # inputs: - # <task specific inputs> - # displayName: '<display name of task>' - - checkout: self - clean: true - - template: SetVersionVariables.yml - parameters: - ReleaseTagVar: $(ReleaseTagVar) - - - pwsh: | - # create folder - sudo mkdir "$(Agent.TempDirectory)/PowerShell" - - # make the current user the owner - sudo chown $env:USER "$(Agent.TempDirectory)/PowerShell" - displayName: 'Create $(Agent.TempDirectory)/PowerShell' - - - template: cloneToOfficialPath.yml - parameters: - nativePathRoot: '$(Agent.TempDirectory)' - - - pwsh: | - tools/releaseBuild/macOS/PowerShellPackageVsts.ps1 -location $(PowerShellRoot) -BootStrap - displayName: 'Bootstrap VM' - env: - __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) - - - template: /tools/releaseBuild/azureDevOps/templates/insert-nuget-config-azfeed.yml - parameters: - repoRoot: $(PowerShellRoot) - - - pwsh: | - $env:AzDevOpsFeedPAT2 = '$(powershellPackageReadPat)' - # Add -SkipReleaseChecks as a mitigation to unblock release. - # macos-10.15 does not allow creating a folder under root. Hence, moving the folder. - $(Build.SourcesDirectory)/tools/releaseBuild/macOS/PowerShellPackageVsts.ps1 -ReleaseTag $(ReleaseTagVar) -Destination $(System.ArtifactsDirectory) -Symbols -location $(PowerShellRoot) -Build -ArtifactName macosBinResults -Runtime 'osx-${{ parameters.buildArchitecture }}' -SkipReleaseChecks - $env:AzDevOpsFeedPAT2 = $null - displayName: 'Build' - env: - __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) - - - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 - displayName: 'Component Detection' - inputs: - sourceScanPath: '$(Build.SourcesDirectory)/tools' - snapshotForceEnabled: true - - - template: /tools/releaseBuild/azureDevOps/templates/step/finalize.yml diff --git a/tools/releaseBuild/azureDevOps/templates/nuget-pkg-sbom.yml b/tools/releaseBuild/azureDevOps/templates/nuget-pkg-sbom.yml deleted file mode 100644 index 0a0e3b96cc1..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/nuget-pkg-sbom.yml +++ /dev/null @@ -1,139 +0,0 @@ -parameters: - - name: PackageVersion - - name: PackagePath - - name: WinFxdPath - - name: LinuxFxdPath - - name: ListOfFiles - type: object - default: - - Microsoft.Management.Infrastructure.CimCmdlets.dll - - Microsoft.PowerShell.Commands.Diagnostics.dll - - Microsoft.PowerShell.Commands.Management.dll - - Microsoft.PowerShell.Commands.Utility.dll - - Microsoft.PowerShell.ConsoleHost.dll - - Microsoft.PowerShell.CoreCLR.Eventing.dll - - Microsoft.PowerShell.Security.dll - - Microsoft.PowerShell.SDK.dll - - Microsoft.WSMan.Management.dll - - Microsoft.WSMan.Runtime.dll - - System.Management.Automation.dll - -steps: - -- template: /.pipelines/templates/insert-nuget-config-azfeed.yml@self - parameters: - repoRoot: $(REPOROOT) - -- pwsh: | - Import-Module "$env:REPOROOT/build.psm1" -Force - Start-PSBootstrap - - $sharedModules = @('Microsoft.PowerShell.Commands.Management', - 'Microsoft.PowerShell.Commands.Utility', - 'Microsoft.PowerShell.ConsoleHost', - 'Microsoft.PowerShell.Security', - 'System.Management.Automation' - ) - - $winOnlyModules = @('Microsoft.Management.Infrastructure.CimCmdlets', - 'Microsoft.PowerShell.Commands.Diagnostics', - 'Microsoft.PowerShell.CoreCLR.Eventing', - 'Microsoft.WSMan.Management', - 'Microsoft.WSMan.Runtime' - ) - - $refAssemblyFolder = Join-Path '$(System.ArtifactsDirectory)' 'RefAssembly' - $null = New-Item -Path $refAssemblyFolder -Force -Verbose -Type Directory - - Start-PSBuild -Clean -Runtime linux-x64 -Configuration Release - - $sharedModules | Foreach-Object { - $refFile = Get-ChildItem -Path "$env:REPOROOT\src\$_\obj\Release\net9.0\refint\$_.dll" - Write-Verbose -Verbose "RefAssembly: $refFile" - Copy-Item -Path $refFile -Destination "$refAssemblyFolder\$_.dll" -Verbose - $refDoc = "$env:REPOROOT\src\$_\bin\Release\net9.0\$_.xml" - if (-not (Test-Path $refDoc)) { - Write-Warning "$refDoc not found" - Get-ChildItem -Path "$env:REPOROOT\src\$_\bin\Release\net9.0\" | Out-String | Write-Verbose -Verbose - } - else { - Copy-Item -Path $refDoc -Destination "$refAssemblyFolder\$_.xml" -Verbose - } - } - - Start-PSBuild -Clean -Runtime win7-x64 -Configuration Release - - $winOnlyModules | Foreach-Object { - $refFile = Get-ChildItem -Path "$env:REPOROOT\src\$_\obj\Release\net9.0\refint\*.dll" - Write-Verbose -Verbose 'RefAssembly: $refFile' - Copy-Item -Path $refFile -Destination "$refAssemblyFolder\$_.dll" -Verbose - $refDoc = "$env:REPOROOT\src\$_\bin\Release\net9.0\$_.xml" - if (-not (Test-Path $refDoc)) { - Write-Warning "$refDoc not found" - Get-ChildItem -Path "$env:REPOROOT\src\$_\bin\Release\net9.0" | Out-String | Write-Verbose -Verbose - } - else { - Copy-Item -Path $refDoc -Destination "$refAssemblyFolder\$_.xml" -Verbose - } - } - - Get-ChildItem $refAssemblyFolder -Recurse | Out-String | Write-Verbose -Verbose - - # Set RefAssemblyPath path variable - $vstsCommandString = "vso[task.setvariable variable=RefAssemblyPath]${refAssemblyFolder}" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - - displayName: Build reference assemblies - env: - __DOTNET_RUNTIME_FEED: $(RUNTIME_SOURCEFEED) - __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) - -- ${{ each value in parameters.ListOfFiles }}: - - pwsh: | - $FileName = '${{ value }}' - $FileBaseName = [System.IO.Path]::GetFileNameWithoutExtension($FileName) - $FilePackagePath = Join-Path -Path '${{ parameters.PackagePath }}' -ChildPath $FileBaseName - $CGManifestPath = Join-Path -Path '${{ parameters.PackagePath }}' -ChildPath 'CGManifest' - Write-Verbose -Verbose "FileName to package: $FileName" - Write-Verbose -Verbose "FilePackage path: $FilePackagePath" - Write-Verbose -Verbose "CGManifest path: $CGManifestPath" - # Set SBOM package name - $vstsCommandString = "vso[task.setvariable variable=SbomFilePackageName]${FileBaseName}" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - # Set SBOM package path variable - $vstsCommandString = "vso[task.setvariable variable=SbomFilePackagePath]${FilePackagePath}" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - # Set CGManifest path variable - $vstsCommandString = "vso[task.setvariable variable=CGManifestPath]${CGManifestPath}" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - # Create Nuget package sources - Import-Module -Name $env:REPOROOT\build.psm1 - Import-Module -Name $env:REPOROOT\tools\packaging - Find-DotNet - New-ILNugetPackageSource -File $FileName -PackagePath '${{ parameters.PackagePath }}' -PackageVersion '${{ parameters.PackageVersion }}' -WinFxdBinPath '${{ parameters.WinFxdPath }}' -LinuxFxdBinPath '${{ parameters.LinuxFxdPath }}' -CGManifestPath $CGManifestPath -RefAssemblyPath $(RefAssemblyPath) - displayName: 'Create NuGet Package source for single file' - - - template: Sbom.yml@ComplianceRepo - parameters: - BuildDropPath: $(SbomFilePackagePath) - Build_Repository_Uri: 'https://github.com/powershell/powershell' - PackageName: $(SbomFilePackageName) - PackageVersion: ${{ parameters.PackageVersion }} - sourceScanPath: $(CGManifestPath) - displayName: SBOM for NuGetPkg - - - pwsh: | - $FileName = '${{ value }}' - $FileBaseName = [System.IO.Path]::GetFileNameWithoutExtension($FileName) - $FilePackagePath = Join-Path -Path '${{ parameters.PackagePath }}' -ChildPath $FileBaseName - Write-Verbose -Verbose "FileName to package: $FileName" - Write-Verbose -Verbose "FilePackage path: $FilePackagePath" - Import-Module -Name $env:REPOROOT\build.psm1 - Import-Module -Name $env:REPOROOT\tools\packaging - Find-DotNet - New-ILNugetPackageFromSource -FileName $FileName -PackageVersion '${{ parameters.PackageVersion }}' -PackagePath '${{ parameters.PackagePath }}' - displayName: 'Create NuGet Package for single file' diff --git a/tools/releaseBuild/azureDevOps/templates/nuget.yml b/tools/releaseBuild/azureDevOps/templates/nuget.yml deleted file mode 100644 index 22f791bf0eb..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/nuget.yml +++ /dev/null @@ -1,290 +0,0 @@ -parameters: - parentJobs: [] - -jobs: -- job: build_nuget - dependsOn: - ${{ parameters.parentJobs }} - displayName: Build NuGet packages - condition: succeeded() - pool: - name: $(windowsPool) - demands: - - ImageOverride -equals PSMMS2019-Secure - - timeoutInMinutes: 90 - - variables: - - name: runCodesignValidationInjection - value: false - - name: NugetSecurityAnalysisWarningLevel - value: none - - name: build - value: ${{ parameters.buildName }} - - group: ESRP - - name: GenAPIToolPath - value: '$(System.ArtifactsDirectory)/GenAPI' - - name: PackagePath - value: '$(System.ArtifactsDirectory)/UnifiedPackagePath' - - name: winFxdPath - value: '$(System.ArtifactsDirectory)/winFxd' - - name: winFxdWinDesktopPath - value: '$(System.ArtifactsDirectory)/winFxdWinDesktop' - - name: linuxFxdPath - value: '$(System.ArtifactsDirectory)/linuxFxd' - - name: alpineFxdPath - value: '$(System.ArtifactsDirectory)/alpineFxd' - - group: DotNetPrivateBuildAccess - - steps: - - checkout: self - clean: true - - - checkout: ComplianceRepo - clean: true - - - template: SetVersionVariables.yml - parameters: - ReleaseTagVar: $(ReleaseTagVar) - - - powershell: | - $content = Get-Content "$env:REPOROOT/global.json" -Raw | ConvertFrom-Json - $vstsCommandString = "vso[task.setvariable variable=SDKVersion]$($content.sdk.version)" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - displayName: 'Find SDK version from global.json' - - - pwsh: | - Import-Module "$env:REPOROOT/build.psm1" -Force - # We just need .NET but we fixed this in an urgent situation. - Start-PSBootStrap -Verbose - displayName: Bootstrap - env: - __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) - - - task: DownloadBuildArtifacts@0 - displayName: 'Download PowerShell build artifacts - finalResults' - inputs: - buildType: current - downloadType: single - artifactName: finalResults - downloadPath: '$(System.ArtifactsDirectory)' - - - task: DownloadBuildArtifacts@0 - displayName: 'Download PowerShell build artifacts - macosPkgResults' - inputs: - buildType: current - downloadType: single - artifactName: macosPkgResults - downloadPath: '$(System.ArtifactsDirectory)' - - - powershell: 'Get-ChildItem $(System.ArtifactsDirectory) -recurse' - displayName: 'Capture downloaded artifacts' - - - powershell: | - $packagePath = (Join-Path $(System.ArtifactsDirectory) packages) - New-Item $packagePath -ItemType Directory -Force > $null - $packages = Get-ChildItem $(System.ArtifactsDirectory) -Include *.zip, *.tar.gz -Recurse - $packages | ForEach-Object { Copy-Item $_.FullName -Destination $packagePath -Verbose } - Get-ChildItem $packagePath -Recurse - displayName: 'Conflate packages to same folder' - - - task: ExtractFiles@1 - displayName: 'Extract files win-fxdependent' - inputs: - archiveFilePatterns: '$(System.ArtifactsDirectory)/packages/PowerShell-*-win-fxdependent.zip' - destinationFolder: '$(winFxdPath)' - - - task: ExtractFiles@1 - displayName: 'Extract files win-fxdependentWinDesktop' - inputs: - archiveFilePatterns: '$(System.ArtifactsDirectory)/packages/PowerShell-*-win-fxdependentWinDesktop.zip' - destinationFolder: '$(winFxdWinDesktopPath)' - - - task: ExtractFiles@1 - displayName: 'Extract files linux-fxdependent' - inputs: - archiveFilePatterns: '$(System.ArtifactsDirectory)/packages/powershell-*-linux-x64-fxdependent.tar.gz' - destinationFolder: '$(linuxFxdPath)' - - - task: ExtractFiles@1 - displayName: 'Extract files alpine-fxdependent' - inputs: - archiveFilePatterns: '$(System.ArtifactsDirectory)/packages/powershell-*-linux-x64-musl-noopt-fxdependent.tar.gz' - destinationFolder: '$(alpineFxdPath)' - - - template: SetVersionVariables.yml - parameters: - ReleaseTagVar: $(ReleaseTagVar) - - - template: shouldSign.yml - - - task: NuGetToolInstaller@1 - displayName: 'Install NuGet.exe' - - # Create nuget packages along with SBOM manifests. - - template: nuget-pkg-sbom.yml - parameters: - PackageVersion: $(Version) - PackagePath: $(PackagePath) - WinFxdPath: $(winFxdPath) - LinuxFxdPath: $(linuxFxdPath) - - - pwsh: | - Get-ChildItem $(linuxFxdPath) - Get-ChildItem $(winFxdPath) - Get-ChildItem $(winFxdWinDesktopPath) - Get-ChildItem $(alpineFxdPath) - displayName: Capture fxd folders - - # Create Global Tool packages along with SBOM manifests - - template: global-tool-pkg-sbom.yml - parameters: - PackageVersion: $(Version) - LinuxBinPath: $(linuxFxdPath) - WindowsBinPath: $(winFxdPath) - WindowsDesktopBinPath: $(winFxdWinDesktopPath) - AlpineBinPath: $(alpineFxdPath) - DestinationPath: $(PackagePath)\globaltool - - - pwsh: | - Get-ChildItem "$(PackagePath)" -Recurse - displayName: Capture generated packages - - - template: EsrpSign.yml@ComplianceRepo - parameters: - buildOutputPath: $(PackagePath) - signOutputPath: $(System.ArtifactsDirectory)\signed - certificateId: "CP-401405" - pattern: | - **\*.nupkg - useMinimatch: true - shouldSign: $(SHOULD_SIGN) - displayName: Sign NuPkg - - - pwsh: | - if (-not (Test-Path '$(System.ArtifactsDirectory)\signed\')) { $null = New-Item -ItemType Directory -Path '$(System.ArtifactsDirectory)\signed\' } - Copy-Item -Path '$(PackagePath)\*.nupkg' -Destination '$(System.ArtifactsDirectory)\signed\' -Verbose -Force - Copy-Item -Path '$(PackagePath)\globaltool\*.nupkg' -Destination '$(System.ArtifactsDirectory)\signed\' -Verbose -Force - displayName: Fake copy when not signing - condition: eq(variables['SHOULD_SIGN'], 'false') - - - pwsh: | - Import-Module "${env:REPOROOT}\build.psm1" -Force - Get-ChildItem -Recurse "$(System.ArtifactsDirectory)\signed\*.nupkg" -Verbose | ForEach-Object { Start-NativeExecution -sb { nuget.exe verify -All $_.FullName } } - displayName: Verify all packages are signed - condition: eq(variables['SHOULD_SIGN'], 'true') - - - task: securedevelopmentteam.vss-secure-development-tools.build-task-antimalware.AntiMalware@3 - displayName: 'Run MpCmdRun.exe' - inputs: - FileDirPath: '$(PackagePath)' - TreatStaleSignatureAs: Warning - - - task: securedevelopmentteam.vss-secure-development-tools.build-task-publishsecurityanalysislogs.PublishSecurityAnalysisLogs@2 - displayName: 'Publish Security Analysis Logs' - - - template: upload-final-results.yml - parameters: - artifactPath: '$(System.ArtifactsDirectory)\signed' - - - pwsh: | - if (-not (Test-Path "$(System.ArtifactsDirectory)\signed\globaltool")) - { - $null = New-Item -Path "$(System.ArtifactsDirectory)\signed\globaltool" -ItemType Directory -Force - } - - Move-Item -Path "$(System.ArtifactsDirectory)\signed\PowerShell.*" -Destination "$(System.ArtifactsDirectory)\signed\globaltool" -Force - Get-ChildItem "$(System.ArtifactsDirectory)\signed\globaltool" -Recurse - displayName: Move global tool packages to subfolder and capture - - - pwsh: | - $packagePath = (Join-Path $(System.ArtifactsDirectory) checksum) - New-Item $packagePath -ItemType Directory -Force > $null - $srcPaths = @("$(System.ArtifactsDirectory)\finalResults", "$(System.ArtifactsDirectory)\macosPkgResults", "$(System.ArtifactsDirectory)\signed") - - $packages = Get-ChildItem -Path $srcPaths -Include *.zip, *.tar.gz, *.msi*, *.pkg, *.deb, *.rpm -Exclude "PowerShell-Symbols*" -Recurse - $packages | ForEach-Object { Copy-Item $_.FullName -Destination $packagePath -Verbose } - - $packagePathList = Get-ChildItem $packagePath -Recurse | Select-Object -ExpandProperty FullName | Out-String - Write-Verbose -Verbose $packagePathList - - $checksums = Get-ChildItem -Path $packagePath -Exclude "SHA512SUMS" | - ForEach-Object { - Write-Verbose -Verbose "Generating checksum file for $($_.FullName)" - $packageName = $_.Name - $hash = (Get-FileHash -Path $_.FullName -Algorithm SHA512).Hash.ToLower() - - # the '*' before the packagename signifies it is a binary - "$hash *$packageName" - } - - $checksums | Out-File -FilePath "$packagePath\SHA512SUMS" -Force - - - $fileContent = Get-Content -Path "$packagePath\SHA512SUMS" -Raw | Out-String - Write-Verbose -Verbose -Message $fileContent - - Copy-Item -Path "$packagePath\SHA512SUMS" -Destination '$(System.ArtifactsDirectory)\signed\' -verbose - displayName: Generate checksum file for packages - - - pwsh: | - $packagePath = (Join-Path $(System.ArtifactsDirectory) checksum_gbltool) - New-Item $packagePath -ItemType Directory -Force > $null - $srcPaths = @("$(System.ArtifactsDirectory)\signed\globaltool") - $packages = Get-ChildItem -Path $srcPaths -Include *.nupkg -Recurse - $packages | ForEach-Object { Copy-Item $_.FullName -Destination $packagePath -Verbose } - - $packagePathList = Get-ChildItem $packagePath -Recurse | Select-Object -ExpandProperty FullName | Out-String - Write-Verbose -Verbose $packagePathList - - $checksums = Get-ChildItem -Path $packagePath -Exclude "SHA512SUMS" | - ForEach-Object { - Write-Verbose -Verbose "Generating checksum file for $($_.FullName)" - $packageName = $_.Name - $hash = (Get-FileHash -Path $_.FullName -Algorithm SHA512).Hash.ToLower() - - # the '*' before the packagename signifies it is a binary - "$hash *$packageName" - } - - $checksums | Out-File -FilePath "$packagePath\SHA512SUMS" -Force - - $fileContent = Get-Content -Path "$packagePath\SHA512SUMS" -Raw | Out-String - Write-Verbose -Verbose -Message $fileContent - - Copy-Item -Path "$packagePath\SHA512SUMS" -Destination '$(System.ArtifactsDirectory)\signed\globaltool\' -verbose - displayName: Generate checksum for global tools - - - template: upload-final-results.yml - parameters: - artifactPath: '$(System.ArtifactsDirectory)\checksum' - artifactFilter: SHA512SUMS - - - task: AzureFileCopy@4 - displayName: 'Upload NuGet packages to Azure' - inputs: - SourcePath: '$(System.ArtifactsDirectory)\signed\*' - azureSubscription: '$(AzureFileCopySubscription)' - Destination: AzureBlob - storage: '$(StorageAccount)' - ContainerName: '$(AzureVersion)-nuget' - condition: and(succeeded(), eq(variables['SHOULD_SIGN'], 'true')) - retryCountOnTaskFailure: 2 - - - task: AzureFileCopy@4 - displayName: 'Upload global tool packages to Azure' - inputs: - sourcePath: '$(System.ArtifactsDirectory)\signed\globaltool\*' - azureSubscription: '$(GlobalToolSubscription)' - Destination: AzureBlob - storage: '$(GlobalToolStorageAccount)' - ContainerName: 'tool-private' - blobPrefix: '$(Version)' - condition: and(succeeded(), eq(variables['SHOULD_SIGN'], 'true')) - retryCountOnTaskFailure: 2 - - - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 - displayName: 'Component Detection' - inputs: - sourceScanPath: '$(PackagePath)' diff --git a/tools/releaseBuild/azureDevOps/templates/release-BuildJson.yml b/tools/releaseBuild/azureDevOps/templates/release-BuildJson.yml deleted file mode 100644 index d183601a06c..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/release-BuildJson.yml +++ /dev/null @@ -1,102 +0,0 @@ -steps: -- checkout: self - clean: true - -- task: DownloadPipelineArtifact@2 - inputs: - source: specific - project: PowerShellCore - pipeline: '696' - preferTriggeringPipeline: true - runVersion: latestFromBranch - runBranch: '$(Build.SourceBranch)' - artifact: BuildInfoJson - path: '$(Pipeline.Workspace)/releasePipeline/BuildInfoJson' - -- pwsh: | - Import-Module '$(Build.SourcesDirectory)/tools/ci.psm1' - $jsonFile = Get-Item "$ENV:PIPELINE_WORKSPACE/releasePipeline/BuildInfoJson/*.json" - $fileName = Split-Path $jsonFile -Leaf - - $dateTime = [datetime]::UtcNow - $dateTime = [datetime]::new($dateTime.Ticks - ($dateTime.Ticks % [timespan]::TicksPerSecond), $dateTime.Kind) - - $metadata = Get-Content ./tools/metadata.json | ConvertFrom-Json - $stableRelease = $metadata.StableRelease.Latest - $ltsRelease = $metadata.LTSRelease.Latest - - Write-Verbose -Verbose "Writing $jsonFile contents:" - $buildInfoJsonContent = Get-Content $jsonFile -Encoding UTF8NoBom -Raw - Write-Verbose -Verbose $buildInfoJsonContent - - $buildInfo = $buildInfoJsonContent | ConvertFrom-Json - $buildInfo.ReleaseDate = $dateTime - - $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 - } else { - Set-BuildVariable -Name CopyMainBuildInfo -Value NO - } - - Set-BuildVariable -Name BuildInfoJsonFile -Value $targetFile - - ## Create 'lts.json' if it's the latest stable and also a LTS release. - - if ($fileName -eq "stable.json") { - 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 - } else { - Set-BuildVariable -Name CopyLTSBuildInfo -Value NO - } - - $releaseTag = $buildInfo.ReleaseTag - $version = $releaseTag -replace '^v' - $semVersion = [System.Management.Automation.SemanticVersion] $version - - $versionFile = "$ENV:PIPELINE_WORKSPACE/$($semVersion.Major)-$($semVersion.Minor).json" - Copy-Item -Path $targetFile -Destination $versionFile -Force - Set-BuildVariable -Name VersionBuildInfoJsonFile -Value $versionFile - Set-BuildVariable -Name CopyVersionBuildInfo -Value YES - } else { - Set-BuildVariable -Name CopyVersionBuildInfo -Value NO - } - displayName: Download and Capture NuPkgs - -- task: AzureFileCopy@4 - displayName: 'AzureBlob build info JSON file Copy' - inputs: - SourcePath: '$(BuildInfoJsonFile)' - azureSubscription: '$(AzureFileCopySubscription)' - Destination: AzureBlob - storage: '$(StorageAccount)' - ContainerName: BuildInfo - condition: and(succeeded(), eq(variables['CopyMainBuildInfo'], 'YES')) - retryCountOnTaskFailure: 2 - -- task: AzureFileCopy@4 - displayName: 'AzureBlob build info ''lts.json'' Copy when needed' - inputs: - SourcePath: '$(LtsBuildInfoJsonFile)' - azureSubscription: '$(AzureFileCopySubscription)' - Destination: AzureBlob - storage: '$(StorageAccount)' - ContainerName: BuildInfo - condition: and(succeeded(), eq(variables['CopyLTSBuildInfo'], 'YES')) - retryCountOnTaskFailure: 2 - -- task: AzureFileCopy@4 - displayName: 'AzureBlob build info ''Major-Minor.json'' Copy when needed' - inputs: - SourcePath: '$(VersionBuildInfoJsonFile)' - azureSubscription: '$(AzureFileCopySubscription)' - Destination: AzureBlob - storage: '$(StorageAccount)' - ContainerName: BuildInfo - condition: and(succeeded(), eq(variables['CopyVersionBuildInfo'], 'YES')) - retryCountOnTaskFailure: 2 diff --git a/tools/releaseBuild/azureDevOps/templates/release-CopyGlobalTools.yml b/tools/releaseBuild/azureDevOps/templates/release-CopyGlobalTools.yml deleted file mode 100644 index 7c9306496ed..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/release-CopyGlobalTools.yml +++ /dev/null @@ -1,56 +0,0 @@ -parameters: -- name: sourceContainerName - type: string - default: 'source-container' - -- name: destinationContainerName - type: string - default: 'destination-container' - -- name: sourceStorageAccountName - type: string - default: 'source-storage-account' - -- name: destinationStorageAccountName - type: string - default: 'destination-storage-account' - -- name: blobPrefix - type: string - default: '$(Version)' - -steps: -- template: release-SetReleaseTagAndContainerName.yml - -- pwsh: | - Import-module '$(BUILD.SOURCESDIRECTORY)/build.psm1' - Install-AzCopy - displayName: Install AzCopy - retryCountOnTaskFailure: 2 - -- pwsh: | - Import-module '$(BUILD.SOURCESDIRECTORY)/build.psm1' - $azcopy = Find-AzCopy - Write-Verbose -Verbose "Found AzCopy: $azcopy" - - $sourceContainerName = "${{ parameters.sourceContainerName }}" - $destinationContainerName = "${{ parameters.destinationContainerName }}" - $sourceStorageAccountName = "${{ parameters.sourceStorageAccountName }}" - $destinationStorageAccountName = "${{ parameters.destinationStorageAccountName }}" - $blobPrefix = "${{ parameters.blobPrefix }}" - - $sourceBlobUrl = "https://${sourceStorageAccountName}.blob.core.windows.net/${sourceContainerName}/${blobPrefix}" - Write-Verbose -Verbose "Source blob url: $sourceBlobUrl" - $destinationBlobUrl = "https://${destinationStorageAccountName}.blob.core.windows.net/${destinationContainerName}" - Write-Verbose -Verbose "Destination blob url: $destinationBlobUrl" - - & $azcopy cp $sourceBlobUrl $destinationBlobUrl --recursive - - $packagesPath = Get-ChildItem -Path $(System.ArtifactsDirectory)\*.deb -Recurse -File | Select-Object -First 1 -ExpandProperty DirectoryName - Write-Host "sending -- vso[task.setvariable variable=PackagesRoot]$packagesPath" - Write-Host "##vso[task.setvariable variable=PackagesRoot]$packagesPath" - - displayName: Copy blobs - retryCountOnTaskFailure: 2 - env: - AZCOPY_AUTO_LOGIN_TYPE: MSI diff --git a/tools/releaseBuild/azureDevOps/templates/release-CreateGitHubDraft.yml b/tools/releaseBuild/azureDevOps/templates/release-CreateGitHubDraft.yml deleted file mode 100644 index 64c4d1b6a24..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/release-CreateGitHubDraft.yml +++ /dev/null @@ -1,110 +0,0 @@ -steps: -- checkout: self - clean: true - -- download: none - -- template: release-SetReleaseTagAndContainerName.yml - -- pwsh: | - Import-module '$(BUILD.SOURCESDIRECTORY)/PowerShell/build.psm1' - Install-AzCopy - displayName: Install AzCopy - retryCountOnTaskFailure: 2 - -- pwsh: | - Import-module '$(BUILD.SOURCESDIRECTORY)/PowerShell/build.psm1' - $azcopy = Find-AzCopy - Write-Verbose -Verbose "Found AzCopy: $azcopy" - - & $azcopy cp https://$(StorageAccount).blob.core.windows.net/$(AzureVersion) $(System.ArtifactsDirectory) --recursive - - $packagesPath = Get-ChildItem -Path $(System.ArtifactsDirectory)\*.deb -Recurse -File | Select-Object -First 1 -ExpandProperty DirectoryName - Write-Host "sending -- vso[task.setvariable variable=PackagesRoot]$packagesPath" - Write-Host "##vso[task.setvariable variable=PackagesRoot]$packagesPath" - - displayName: Download Azure Artifacts - retryCountOnTaskFailure: 2 - env: - AZCOPY_AUTO_LOGIN_TYPE: MSI - -- pwsh: | - Get-ChildItem $(System.ArtifactsDirectory)\* -recurse | Select-Object -ExpandProperty FullName - displayName: Capture downloaded artifacts - -- pwsh: | - git clone https://$(AzureDevOpsPat)@mscodehub.visualstudio.com/PowerShellCore/_git/Internal-PowerShellTeam-Tools '$(Pipeline.Workspace)/tools' - displayName: Clone Internal-Tools repository - -- pwsh: | - $Path = "$(PackagesRoot)" - $OutputPath = Join-Path $Path ‘hashes.sha256’ - $srcPaths = @($Path) - $packages = Get-ChildItem -Path $srcPaths -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 - -- checkout: ComplianceRepo - -- pwsh: | - $releaseVersion = '$(ReleaseTag)' -replace '^v','' - $vstsCommandString = "vso[task.setvariable variable=ReleaseVersion]$releaseVersion" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - displayName: 'Set release version' - -- template: Sbom.yml@ComplianceRepo - parameters: - BuildDropPath: '$(PackagesRoot)' - Build_Repository_Uri: 'https://github.com/powershell/powershell.git' - displayName: PowerShell Hashes SBOM - packageName: PowerShell Artifact Hashes - packageVersion: $(ReleaseVersion) - sourceScanPath: '$(PackagesRoot)' - -- pwsh: | - Import-module '$(Pipeline.Workspace)/tools/Scripts/GitHubRelease.psm1' - $releaseVersion = '$(ReleaseTag)' -replace '^v','' - $semanticVersion = [System.Management.Automation.SemanticVersion]$releaseVersion - - $isPreview = $semanticVersion.PreReleaseLabel -ne $null - - $fileName = if ($isPreview) { - "preview.md" - } - else { - $semanticVersion.Major.ToString() + "." + $semanticVersion.Minor.ToString() + ".md" - } - - $filePath = "$env:BUILD_SOURCESDIRECTORY/PowerShell/CHANGELOG/$fileName" - Write-Verbose -Verbose "Selected Log file: $filePath" - - if (-not (Test-Path $filePath)) { - throw "$filePath not found" - } - - $changelog = Get-Content -Path $filePath - - $startPattern = "^## \[" + ([regex]::Escape($releaseVersion)) + "\]" - $endPattern = "^## \[{0}\.{1}\.{2}*" -f $semanticVersion.Major, $semanticVersion.Minor, $semanticVersion.Patch - - $clContent = $changelog | ForEach-Object { - if ($_ -match $startPattern) { $outputLine = $true } - elseif ($_ -match $endPattern) { $outputLine = $false } - if ($outputLine) { $_} - } | 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 $(PackagesRoot) -Token $(GitHubReleasePat) - displayName: Publish Release Draft diff --git a/tools/releaseBuild/azureDevOps/templates/release-GlobalToolTest.yml b/tools/releaseBuild/azureDevOps/templates/release-GlobalToolTest.yml deleted file mode 100644 index 8591791de0e..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/release-GlobalToolTest.yml +++ /dev/null @@ -1,149 +0,0 @@ -parameters: - jobName: "" - displayName: "" - imageName: "" - globalToolExeName: 'pwsh.exe' - globalToolPackageName: 'PowerShell.Windows.x64' - - -jobs: -- job: ${{ parameters.jobName }} - displayName: ${{ parameters.displayName }} - pool: - # test - vmImage: ${{ parameters.imageName }} - variables: - - group: DotNetPrivateBuildAccess - - steps: - - checkout: self - clean: true - - - task: DownloadPipelineArtifact@2 - inputs: - source: specific - project: PowerShellCore - pipeline: '696' - preferTriggeringPipeline: true - runVersion: latestFromBranch - runBranch: '$(Build.SourceBranch)' - artifact: finalResults - patterns: '**/*.nupkg' - path: '$(Pipeline.Workspace)/releasePipeline/finalResults' - - - pwsh: | - $dotnetMetadataPath = "$(Build.SourcesDirectory)/DotnetRuntimeMetadata.json" - $dotnetMetadataJson = Get-Content $dotnetMetadataPath -Raw | ConvertFrom-Json - - # Channel is like: $Channel = "5.0.1xx-preview2" - $Channel = $dotnetMetadataJson.sdk.channel - - $sdkVersion = (Get-Content "$(Build.SourcesDirectory)/global.json" -Raw | ConvertFrom-Json).sdk.version - Import-Module "$(Build.SourcesDirectory)/build.psm1" -Force - - Find-Dotnet - - if(-not (Get-PackageSource -Name 'dotnet' -ErrorAction SilentlyContinue)) - { - $nugetFeed = ([xml](Get-Content $(Build.SourcesDirectory)/nuget.config -Raw)).Configuration.packagesources.add | Where-Object { $_.Key -eq 'dotnet' } | Select-Object -ExpandProperty Value - if ($nugetFeed) { - Register-PackageSource -Name 'dotnet' -Location $nugetFeed -ProviderName NuGet - Write-Verbose -Message "Register new package source 'dotnet'" -verbose - } - } - - ## Install latest version from the channel - - #Install-Dotnet -Channel "$Channel" -Version $sdkVersion - Start-PSBootstrap - - Write-Verbose -Message "Installing .NET SDK completed." -Verbose - - displayName: Install .NET - env: - __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) - - - pwsh: | - $branch = $ENV:BUILD_SOURCEBRANCH - $version = $branch -replace '^.*(release[-/])v' - $vstsCommandString = "vso[task.setvariable variable=PowerShellVersion]$version" - Write-Verbose -Message "Version is $version" -Verbose - Write-Host -Object "##$vstsCommandString" - displayName: Set PowerShell Version - - - pwsh: | - $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 - Import-Module "$(Build.SourcesDirectory)/build.psm1" -Force - Start-PSBootstrap - - $toolPath = New-Item -ItemType Directory "$(System.DefaultWorkingDirectory)/toolPath" | Select-Object -ExpandProperty FullName - - dotnet tool install --add-source "$ENV:PIPELINE_WORKSPACE/releasePipeline/finalResults" --tool-path $toolPath --version '$(PowerShellVersion)' '${{ parameters.globalToolPackageName }}' - - Get-ChildItem -Path $toolPath - - displayName: Install global tool - env: - __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) - - - pwsh: | - $toolPath = "$(System.DefaultWorkingDirectory)/toolPath/${{ parameters.globalToolExeName }}" - - if (-not (Test-Path $toolPath)) - { - throw "Tool is not installed at $toolPath" - } - else - { - Write-Verbose -Verbose "Tool found at: $toolPath" - } - displayName: Validate tool is installed - - - pwsh: | - Import-Module "$(Build.SourcesDirectory)/build.psm1" -Force - Start-PSBootstrap - - $exeName = if ($IsWindows) { "pwsh.exe" } else { "pwsh" } - - $toolPath = "$(System.DefaultWorkingDirectory)/toolPath/${{ parameters.globalToolExeName }}" - - $source = (get-command -Type Application -Name dotnet | Select-Object -First 1 -ExpandProperty source) - $target = (Get-ChildItem $source).target - - # If we find a symbolic link for dotnet, then we need to split the filename off the target. - if ($target) { - Write-Verbose -Verbose "Splitting target: $target" - $target = Split-Path $target - } - - Write-Verbose -Verbose "target is set as $target" - - $env:DOTNET_ROOT = (resolve-path -Path (Join-Path (split-path $source) $target)).ProviderPath - - Write-Verbose -Verbose "DOTNET_ROOT: $env:DOTNET_ROOT" - Get-ChildItem $env:DOTNET_ROOT - - $versionFound = & $toolPath -c '$PSVersionTable.PSVersion.ToString()' - - if ( '$(PowerShellVersion)' -ne $versionFound) - { - throw "Expected version of global tool not found. Installed version is $versionFound" - } - else - { - write-verbose -verbose "Found expected version: $versionFound" - } - - $dateYear = & $toolPath -c '(Get-Date).Year' - - if ( $dateYear -ne [DateTime]::Now.Year) - { - throw "Get-Date returned incorrect year: $dateYear" - } - else - { - write-verbose -verbose "Got expected year: $dateYear" - } - displayName: Basic validation - env: - __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) diff --git a/tools/releaseBuild/azureDevOps/templates/release-MakeContainerPublic.yml b/tools/releaseBuild/azureDevOps/templates/release-MakeContainerPublic.yml deleted file mode 100644 index 65d5ea50191..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/release-MakeContainerPublic.yml +++ /dev/null @@ -1,20 +0,0 @@ -steps: -- download: none - -- template: release-SetReleaseTagAndContainerName.yml - -- pwsh: | - az login --service-principal -u $(az_url) -p $(az_key) --tenant $(az_name) - displayName: az login - -- pwsh: | - az storage container set-permission --account-name $(StorageAccount) --name $(azureVersion) --public-access blob - displayName: Make container public - -- pwsh: | - az storage container set-permission --account-name $(StorageAccount) --name $(azureVersion)-gc --public-access blob - displayName: Make guest configuration miminal package container public - -- pwsh: | - az logout - displayName: az logout diff --git a/tools/releaseBuild/azureDevOps/templates/release-MsixBundle.yml b/tools/releaseBuild/azureDevOps/templates/release-MsixBundle.yml deleted file mode 100644 index a9591b2d251..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/release-MsixBundle.yml +++ /dev/null @@ -1,81 +0,0 @@ -jobs: -- job: CreateMSIXBundle - displayName: Create .msixbundle file - - pool: - name: PowerShell1ES - demands: - - ImageOverride -equals PSMMS2019-Secure - - variables: - - group: msixTools - - group: 'Azure Blob variable group' - - steps: - - template: release-SetReleaseTagAndContainerName.yml - - - task: DownloadPipelineArtifact@2 - retryCountOnTaskFailure: 2 - inputs: - source: specific - project: PowerShellCore - pipeline: '696' - preferTriggeringPipeline: true - runVersion: latestFromBranch - runBranch: '$(Build.SourceBranch)' - artifact: finalResults - patterns: '**/*.msix' - path: '$(Pipeline.Workspace)\releasePipeline\msix' - - - 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 - Invoke-RestMethod -Uri '$(makeappUrl)' -OutFile "$toolsDir\makeappx.zip" - Expand-Archive "$toolsDir\makeappx.zip" -DestinationPath "$toolsDir\makeappx" -Force - $exePath = "$toolsDir\makeappx\makeappx.exe" - - Write-Verbose -Verbose 'makeappx was installed:' - Get-ChildItem -Path $toolsDir -Recurse - } - - $vstsCommandString = "vso[task.setvariable variable=MakeAppxPath]$exePath" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - displayName: Install makeappx tool - retryCountOnTaskFailure: 1 - - - pwsh: | - $sourceDir = '$(Pipeline.Workspace)\releasePipeline\msix' - $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: AzureFileCopy@4 - displayName: 'Upload MSIX Bundle package to Az Blob' - retryCountOnTaskFailure: 2 - inputs: - SourcePath: '$(BundleDir)/*.msixbundle' - azureSubscription: '$(AzureFileCopySubscription)' - Destination: AzureBlob - storage: '$(StorageAccount)' - ContainerName: '$(AzureVersion)-private' - resourceGroup: '$(StorageResourceGroup)' - condition: succeeded() diff --git a/tools/releaseBuild/azureDevOps/templates/release-PublishPackageMsftCom.yml b/tools/releaseBuild/azureDevOps/templates/release-PublishPackageMsftCom.yml deleted file mode 100644 index 861cf48c35a..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/release-PublishPackageMsftCom.yml +++ /dev/null @@ -1,57 +0,0 @@ -parameters: - - name: skipPublish - default: false - type: boolean - -steps: -- template: release-SetReleaseTagAndContainerName.yml - -- pwsh: | - $packageVersion = '$(ReleaseTag)'.ToLowerInvariant() -replace '^v','' - $vstsCommandString = "vso[task.setvariable variable=packageVersion]$packageVersion" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - displayName: Set Package version - -- pwsh: | - $branch = 'main-mirror' - $gitArgs = "clone", - "--verbose", - "--branch", - "$branch", - "https://$(mscodehubCodeReadPat)@mscodehub.visualstudio.com/PowerShellCore/_git/Internal-PowerShellTeam-Tools", - '$(Pipeline.Workspace)/tools' - $gitArgs | Write-Verbose -Verbose - git $gitArgs - displayName: Clone Internal-PowerShellTeam-Tools from MSCodeHub - -- task: PipAuthenticate@1 - inputs: - artifactFeeds: 'pmc' - pythonDownloadServiceConnections: pmcDownload - -- pwsh: | - pip install pmc-cli - - $newPath = (resolve-path '~/.local/bin').providerpath - $vstsCommandString = "vso[task.setvariable variable=PATH]${env:PATH}:$newPath" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - displayName: Install pmc cli - -- pwsh: | - $metadata = Get-Content -Path "$(Build.SourcesDirectory)/tools/metadata.json" -Raw | ConvertFrom-Json - $params = @{ - ReleaseTag = "$(ReleaseTag)" - AadClientId = "$(PmcCliClientID)" - BlobFolderName = "$(AzureVersion)" - LTS = $metadata.LTSRelease.Latest - ForProduction = $true - SkipPublish = $${{ parameters.skipPublish }} - MappingFilePath = '$(System.DefaultWorkingDirectory)/tools/packages.microsoft.com/mapping.json' - } - - $params | Out-String -width 9999 -Stream | write-Verbose -Verbose - - & '$(Pipeline.Workspace)/tools/packages.microsoft.com-v4/releaseLinuxPackages.ps1' @params - displayName: Run release script diff --git a/tools/releaseBuild/azureDevOps/templates/release-PublishSymbols.yml b/tools/releaseBuild/azureDevOps/templates/release-PublishSymbols.yml deleted file mode 100644 index db2cc86e259..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/release-PublishSymbols.yml +++ /dev/null @@ -1,51 +0,0 @@ -steps: -- task: DownloadPipelineArtifact@2 - inputs: - source: specific - project: PowerShellCore - pipeline: '696' - preferTriggeringPipeline: true - runVersion: latestFromBranch - runBranch: '$(Build.SourceBranch)' - artifact: results - path: '$(Pipeline.Workspace)\results' - itemPattern: | - **/* - !**/*signed.zip - -- pwsh: | - Write-Verbose -Verbose "Enumerating $(Pipeline.Workspace)\results" - $downloadedArtifacts = Get-ChildItem -Recurse "$(Pipeline.Workspace)\results" - $downloadedArtifacts - $expandedRoot = New-Item -Path "$(Pipeline.Workspace)/expanded" -ItemType Directory -Verbose - $symbolsRoot = New-Item -Path "$(Pipeline.Workspace)/symbols" -ItemType Directory -Verbose - - $downloadedArtifacts | ForEach-Object { - $destFolder = New-Item -Path "$expandedRoot/$($_.BaseName)/" -ItemType Directory -Verbose - Expand-Archive -Path $_.FullName -DestinationPath $destFolder -Force - - $symbolsZipFile = Join-Path -Path $destFolder -ChildPath "symbols.zip" - $symbolZipFileContents = New-Item -Path "$destFolder/Symbols-$($_.BaseName)" -ItemType Directory -Verbose - Expand-Archive -Path $symbolsZipFile -DestinationPath $symbolZipFileContents -Force - - $symbolsToPublish = New-Item -Path "$symbolsRoot/$($_.BaseName)" -ItemType Directory -Verbose - - Get-ChildItem -Path $symbolZipFileContents -Recurse -Filter '*.pdb' | ForEach-Object { - Copy-Item -Path $_.FullName -Destination $symbolsToPublish -Verbose - } - } - - Write-Verbose -Verbose "Enumerating $symbolsRoot" - Get-ChildItem -Path $symbolsRoot -Recurse - $vstsCommandString = "vso[task.setvariable variable=SymbolsPath]$symbolsRoot" - Write-Verbose -Message "$vstsCommandString" -Verbose - Write-Host -Object "##$vstsCommandString" - displayName: Expand and capture symbols folders -- task: PublishSymbols@2 - inputs: - symbolsFolder: '$(SymbolsPath)' - searchPattern: '**/*.pdb' - indexSources: false - publishSymbols: true - symbolServerType: teamServices - detailedLog: true diff --git a/tools/releaseBuild/azureDevOps/templates/release-ReleaseToNuGet.yml b/tools/releaseBuild/azureDevOps/templates/release-ReleaseToNuGet.yml deleted file mode 100644 index 33a72f56bbb..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/release-ReleaseToNuGet.yml +++ /dev/null @@ -1,56 +0,0 @@ -parameters: - - name: skipPublish - default: false - type: boolean - -steps: -- task: DownloadPipelineArtifact@2 - condition: and(eq('${{ parameters.skipPublish }}', 'false'), succeeded()) - inputs: - source: specific - project: PowerShellCore - pipeline: '696' - preferTriggeringPipeline: true - runVersion: latestFromBranch - runBranch: '$(Build.SourceBranch)' - artifact: finalResults - patterns: '**/*.nupkg' - path: '$(Pipeline.Workspace)/releasePipeline/finalResults' - -- task: DownloadPipelineArtifact@2 - condition: and(eq('${{ parameters.skipPublish }}', 'false'), succeeded()) - inputs: - source: specific - project: PowerShellCore - pipeline: '696' - preferTriggeringPipeline: true - runVersion: latestFromBranch - runBranch: '$(Build.SourceBranch)' - artifact: metadata - path: '$(Pipeline.Workspace)/releasePipeline/metadata' - -- pwsh: | - #Exclude all global tool packages. Their names start with 'PowerShell.' - $null = New-Item -ItemType Directory -Path "$(Pipeline.Workspace)/release" - Copy-Item "$ENV:PIPELINE_WORKSPACE/releasePipeline/finalResults/*.nupkg" -Destination "$(Pipeline.Workspace)/release" -Exclude "PowerShell.*.nupkg" -Force -Verbose - - $releaseVersion = Get-Content "$ENV:PIPELINE_WORKSPACE/releasePipeline/metadata/release.json" | ConvertFrom-Json | Select-Object -ExpandProperty 'ReleaseVersion' - $globalToolPath = "$ENV:PIPELINE_WORKSPACE/releasePipeline/finalResults/PowerShell.$releaseVersion.nupkg" - - if ($releaseVersion -notlike '*-*') { - # Copy the global tool package for stable releases - Copy-Item $globalToolPath -Destination "$(Pipeline.Workspace)/release" - } - - Get-ChildItem "$(Pipeline.Workspace)/release" -recurse - displayName: Download and capture nupkgs - condition: and(eq('${{ parameters.skipPublish }}', 'false'), succeeded()) - -- task: NuGetCommand@2 - displayName: 'NuGet push' - condition: and(eq('${{ parameters.skipPublish }}', 'false'), succeeded()) - inputs: - command: push - packagesToPush: '$(Pipeline.Workspace)/release/*.nupkg' - nuGetFeedType: external - publishFeedCredentials: PowerShellNuGetOrgPush diff --git a/tools/releaseBuild/azureDevOps/templates/release-SDKTests.yml b/tools/releaseBuild/azureDevOps/templates/release-SDKTests.yml deleted file mode 100644 index 93fb0bf07cb..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/release-SDKTests.yml +++ /dev/null @@ -1,148 +0,0 @@ -parameters: - jobName: "" - displayName: "" - imageName: "" - -jobs: -- job: ${{ parameters.jobName }} - displayName: ${{ parameters.displayName }} - pool: - # testing - vmImage: ${{ parameters.imageName }} - variables: - - group: mscodehub-feed-read-general - - group: mscodehub-feed-read-akv - - group: DotNetPrivateBuildAccess - steps: - - checkout: self - clean: true - - - task: DownloadPipelineArtifact@2 - inputs: - source: specific - project: PowerShellCore - pipeline: '696' - preferTriggeringPipeline: true - runVersion: latestFromBranch - runBranch: '$(Build.SourceBranch)' - artifact: finalResults - patterns: '**/*.nupkg' - path: '$(Pipeline.Workspace)/releasePipeline/finalResults' - - - task: DownloadPipelineArtifact@2 - inputs: - source: specific - project: PowerShellCore - pipeline: '696' - preferTriggeringPipeline: true - runVersion: latestFromBranch - runBranch: '$(Build.SourceBranch)' - artifact: metadata - path: '$(Pipeline.Workspace)/releasePipeline/metadata' - - - template: /.pipelines/templates/insert-nuget-config-azfeed.yml@self - parameters: - repoRoot: $(Build.SourcesDirectory) - - - pwsh: | - Import-Module "$(Build.SourcesDirectory)/build.psm1" -Force - - Write-Verbose -Verbose "Capture hosting folder files" - Get-ChildItem '$(Build.SourcesDirectory)/test/hosting' - - # The above cmdlet creates a lower-case nuget.config. There also exists a NuGet.config which we needed to replace. - # Hence the following workaround - - if (-not $IsWindows) { - Move-Item -Path '$(Build.SourcesDirectory)/test/hosting/nuget.config' -Destination '$(Build.SourcesDirectory)/test/hosting/NuGet.Config' -Force -ErrorAction Continue - Write-Verbose -Verbose "Capture hosting folder files after Move-Item" - Get-ChildItem '$(Build.SourcesDirectory)/test/hosting' - } - - if(-not (Test-Path "$(Build.SourcesDirectory)/test/hosting/NuGet.Config")) - { - throw "NuGet.Config is not created" - } - else - { - Write-Verbose -Verbose "Capture NuGet.Config contents" - Get-Content "$(Build.SourcesDirectory)/test/hosting/NuGet.Config" -Raw - } - displayName: Insert internal nuget feed - - - pwsh: | - $dotnetMetadataPath = "$(Build.SourcesDirectory)/DotnetRuntimeMetadata.json" - $dotnetMetadataJson = Get-Content $dotnetMetadataPath -Raw | ConvertFrom-Json - - # Channel is like: $Channel = "5.0.1xx-preview2" - $Channel = $dotnetMetadataJson.sdk.channel - - $sdkVersion = (Get-Content "$(Build.SourcesDirectory)/global.json" -Raw | ConvertFrom-Json).sdk.version - Import-Module "$(Build.SourcesDirectory)/build.psm1" -Force - - Find-Dotnet - - if(-not (Get-PackageSource -Name 'dotnet' -ErrorAction SilentlyContinue)) - { - $nugetFeed = ([xml](Get-Content $(Build.SourcesDirectory)/nuget.config -Raw)).Configuration.packagesources.add | Where-Object { $_.Key -eq 'dotnet' } | Select-Object -ExpandProperty Value - - if ($nugetFeed) { - Register-PackageSource -Name 'dotnet' -Location $nugetFeed -ProviderName NuGet - Write-Verbose -Message "Register new package source 'dotnet'" -verbose - } - } - - ## Install latest version from the channel - #Install-Dotnet -Channel "$Channel" -Version $sdkVersion - - Start-PSBootstrap - - Write-Verbose -Message "Installing .NET SDK completed." -Verbose - - displayName: Install .NET - env: - __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) - - - pwsh: | - $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 - Import-Module "$(Build.SourcesDirectory)/build.psm1" -Force - Start-PSBootstrap - - $localLocation = "$(Pipeline.Workspace)/releasePipeline/finalResults" - $xmlElement = @" - <add key=`"local`" value=`"$localLocation`" /> - <add key="dotnet" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet9/nuget/v2" /> - </packageSources> - "@ - - $releaseVersion = Get-Content "$(Pipeline.Workspace)/releasePipeline/metadata/release.json" | ConvertFrom-Json | Select-Object -ExpandProperty 'ReleaseVersion' - - Set-Location -Path $(Build.SourcesDirectory)/test/hosting - - Get-ChildItem - - ## register the packages download directory in the nuget file - $nugetConfigContent = Get-Content ./NuGet.Config -Raw - $updateNugetContent = $nugetConfigContent.Replace("</packageSources>", $xmlElement) - - $updateNugetContent | Out-File ./NuGet.Config -Encoding ascii - - Get-Content ./NuGet.Config - - # Add workaround to unblock xUnit testing see issue: https://github.com/dotnet/sdk/issues/26462 - $dotnetPath = if ($IsWindows) { "$env:LocalAppData\Microsoft\dotnet" } else { "$env:HOME/.dotnet" } - $env:DOTNET_ROOT = $dotnetPath - - dotnet --info - dotnet restore - dotnet test /property:RELEASE_VERSION=$releaseVersion --test-adapter-path:. "--logger:xunit;LogFilePath=$(System.DefaultWorkingDirectory)/test-hosting.xml" - - displayName: Restore and execute tests - env: - __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) - - - task: PublishTestResults@2 - displayName: 'Publish Test Results **\test-hosting.xml' - inputs: - testResultsFormat: XUnit - testResultsFiles: '**\test-hosting.xml' diff --git a/tools/releaseBuild/azureDevOps/templates/release-SetReleaseTagAndContainerName.yml b/tools/releaseBuild/azureDevOps/templates/release-SetReleaseTagAndContainerName.yml deleted file mode 100644 index 7e88624b45c..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/release-SetReleaseTagAndContainerName.yml +++ /dev/null @@ -1,26 +0,0 @@ -steps: -- pwsh: | - $variable = 'releaseTag' - $branch = $ENV:BUILD_SOURCEBRANCH - if($branch -notmatch '^.*((release/|rebuild/.*rebuild))') - { - throw "Branch name is not in release format: '$branch'" - } - - $releaseTag = $Branch -replace '^.*((release|rebuild)/)' - $vstsCommandString = "vso[task.setvariable variable=$Variable]$releaseTag" - Write-Verbose -Message "setting $Variable to $releaseTag" -Verbose - Write-Host -Object "##$vstsCommandString" - displayName: Set Release Tag - -- pwsh: | - $azureVersion = '$(ReleaseTag)'.ToLowerInvariant() -replace '\.', '-' - $vstsCommandString = "vso[task.setvariable variable=AzureVersion]$azureVersion" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - - $version = '$(ReleaseTag)'.ToLowerInvariant().Substring(1) - $vstsCommandString = "vso[task.setvariable variable=Version]$version" - Write-Host ("sending " + $vstsCommandString) - Write-Host "##$vstsCommandString" - displayName: Set container name diff --git a/tools/releaseBuild/azureDevOps/templates/release-UpdateDepsJson.yml b/tools/releaseBuild/azureDevOps/templates/release-UpdateDepsJson.yml deleted file mode 100644 index fa42064602e..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/release-UpdateDepsJson.yml +++ /dev/null @@ -1,71 +0,0 @@ -jobs: -- job: UpdateDepsFiles - displayName: Update deps files - - pool: - name: PowerShell1ES - demands: - - ImageOverride -equals PSMMS2019-Secure - - variables: - - group: 'Azure Blob variable group' - steps: - - checkout: self - clean: true - - - task: DownloadPipelineArtifact@2 - inputs: - source: specific - project: PowerShellCore - pipeline: '696' - preferTriggeringPipeline: true - runVersion: latestFromBranch - runBranch: '$(Build.SourceBranch)' - artifact: finalResults - patterns: '**/PowerShell*-win-x64.zip' - path: '$(Pipeline.Workspace)/releasePipeline/finalResults' - - - task: DownloadPipelineArtifact@2 - inputs: - source: specific - project: PowerShellCore - pipeline: '696' - preferTriggeringPipeline: true - runVersion: latestFromBranch - runBranch: '$(Build.SourceBranch)' - artifact: BuildInfoJson - path: '$(Pipeline.Workspace)/releasePipeline/BuildInfoJson' - - - pwsh: | - $fileName = (Get-Item "$ENV:PIPELINE_WORKSPACE/releasePipeline/BuildInfoJson/*.json").BaseName - if ($fileName -notin 'stable','preview') - { - throw "Unexpected fileName: $fileName" - } - - $vstsCommand = "vso[task.setvariable variable=BlobPrefix]$fileName" - Write-Verbose -Verbose $vstsCommand - Write-Host "##$vstsCommand" - displayName: Determine container name - - - pwsh: | - $zipFile = Get-Item "$ENV:PIPELINE_WORKSPACE/releasePipeline/finalResults/PowerShell*-win-x64.zip" -Exclude *-symbols-* - Write-Verbose -Verbose "zipFile: $zipFile" - Expand-Archive -Path $zipFile -Destination "$ENV:PIPELINE_WORKSPACE/expanded" - - $pwshDepsFile = Get-Item "$ENV:PIPELINE_WORKSPACE/expanded/pwsh.deps.json" - $vstsCommand = "vso[task.setvariable variable=FileToUpload]$pwshDepsFile" - Write-Verbose -Verbose $vstsCommand - Write-Host "##$vstsCommand" - displayName: Determine file to upload - - - task: AzureFileCopy@4 - displayName: 'AzureBlob pwsh.deps.json file Copy' - inputs: - SourcePath: '$(FileToUpload)' - azureSubscription: '$(AzureFileCopySubscription)' - Destination: AzureBlob - storage: '$(StorageAccount)' - ContainerName: ps-deps-json - blobPrefix: '$(BlobPrefix)' - retryCountOnTaskFailure: 2 diff --git a/tools/releaseBuild/azureDevOps/templates/release-ValidateFxdPackage.yml b/tools/releaseBuild/azureDevOps/templates/release-ValidateFxdPackage.yml deleted file mode 100644 index 7f2c816a20f..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/release-ValidateFxdPackage.yml +++ /dev/null @@ -1,92 +0,0 @@ -parameters: - jobName: "" - displayName: "" - imageName: "" - packageNamePattern: "" - use1ES: false - -jobs: -- job: ${{ parameters.jobName }} - displayName: ${{ parameters.displayName }} - variables: - - group: DotNetPrivateBuildAccess - pool: - ${{ if eq(parameters.use1ES, 'false') }}: - vmImage: ${{ parameters.imageName }} - ${{ else }}: - name: 'PS-MSCodeHub-ARM' # add ImageOverride to select image - steps: - - checkout: self - clean: true - - - task: DownloadPipelineArtifact@2 - inputs: - source: specific - project: PowerShellCore - pipeline: '696' - preferTriggeringPipeline: true - runVersion: latestFromBranch - runBranch: '$(Build.SourceBranch)' - artifact: finalResults - patterns: '${{ parameters.packageNamePattern }}' - path: '$(Pipeline.Workspace)/releasePipeline/finalResults' - - - pwsh: | - $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 - Import-Module "$(Build.SourcesDirectory)/build.psm1" -Force - Start-PSBootstrap - Write-Verbose -Message "Installing .NET SDK completed." -Verbose - displayName: Install .NET - env: - __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) - - - pwsh: | - Get-ChildItem -Path '$(Pipeline.Workspace)/releasePipeline/finalResults' -Recurse - displayName: Capture downloaded package - - - pwsh: | - $destPath = New-Item '$(Pipeline.Workspace)/releasePipeline/finalResults/fxd' -ItemType Directory - $packageNameFilter = '${{ parameters.packageNamePattern }}' - - if ($packageNameFilter.EndsWith('tar.gz')) { - $package = @(Get-ChildItem -Path '$(Pipeline.Workspace)/releasePipeline/finalResults/*.tar.gz') - Write-Verbose -Verbose "Package: $package" - if ($package.Count -ne 1) { - throw 'Only 1 package was expected.' - } - tar -xvf $package.FullName -C $destPath - } - else { - $package = @(Get-ChildItem -Path '$(Pipeline.Workspace)/releasePipeline/finalResults/*.zip') - Write-Verbose -Verbose "Package: $package" - if ($package.Count -ne 1) { - throw 'Only 1 package was expected.' - } - Expand-Archive -Path $package.FullName -Destination "$destPath" -Verbose - } - displayName: Expand fxd package - - - pwsh: | - $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 - Import-Module "$(Build.SourcesDirectory)/build.psm1" -Force - Find-Dotnet -SetDotnetRoot - Write-Verbose -Verbose "DOTNET_ROOT: $env:DOTNET_ROOT" - Write-Verbose -Verbose "Check dotnet install" - dotnet --info - Write-Verbose -Verbose "Start test" - $packageNameFilter = '${{ parameters.packageNamePattern }}' - $pwshExeName = if ($packageNameFilter.EndsWith('tar.gz')) { 'pwsh' } else { 'pwsh.exe' } - $pwshPath = Join-Path '$(Pipeline.Workspace)/releasePipeline/finalResults/fxd' $pwshExeName - - if ($IsLinux) { - chmod u+x $pwshPath - } - - $pwshDllPath = Join-Path '$(Pipeline.Workspace)/releasePipeline/finalResults/fxd' 'pwsh.dll' - - $actualOutput = & dotnet $pwshDllPath -c 'Start-ThreadJob -ScriptBlock { "1" } | Wait-Job | Receive-Job' - Write-Verbose -Verbose "Actual output: $actualOutput" - if ($actualOutput -ne 1) { - throw "Actual output is not as expected" - } - displayName: Test package diff --git a/tools/releaseBuild/azureDevOps/templates/release-ValidatePackageBOM.yml b/tools/releaseBuild/azureDevOps/templates/release-ValidatePackageBOM.yml deleted file mode 100644 index a7217968575..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/release-ValidatePackageBOM.yml +++ /dev/null @@ -1,49 +0,0 @@ -steps: -- checkout: self - clean: true - -- pwsh: | - Get-ChildItem ENV: | Out-String -width 9999 -Stream | write-Verbose -Verbose - displayName: Capture environment - -- template: release-SetReleaseTagAndContainerName.yml - -- pwsh: | - $name = "{0}_{1:x}" -f '$(releaseTag)', (Get-Date).Ticks - Write-Host $name - Write-Host "##vso[build.updatebuildnumber]$name" - displayName: Set Release Name - -- task: DownloadPipelineArtifact@2 - inputs: - source: specific - project: PowerShellCore - pipeline: '696' - preferTriggeringPipeline: true - runVersion: latestFromBranch - runBranch: '$(Build.SourceBranch)' - artifact: finalResults - path: $(System.ArtifactsDirectory) - - -- pwsh: | - Get-ChildItem $(System.ArtifactsDirectory)\* -recurse | Select-Object -ExpandProperty Name - displayName: Capture Artifact Listing - -- pwsh: | - Install-module Pester -Scope CurrentUser -Force -MaximumVersion 4.99 - displayName: Install Pester - condition: succeededOrFailed() - -- pwsh: | - Import-module './build.psm1' - Import-module './tools/packaging' - $env:PACKAGE_FOLDER = '$(System.ArtifactsDirectory)' - $path = Join-Path -Path $pwd -ChildPath './packageReleaseTests.xml' - $results = invoke-pester -Script './tools/packaging/releaseTests' -OutputFile $path -OutputFormat NUnitXml -PassThru - Write-Host "##vso[results.publish type=NUnit;mergeResults=true;runTitle=Package Release Tests;publishRunAttachments=true;resultFiles=$path;]" - if($results.TotalCount -eq 0 -or $results.FailedCount -gt 0) - { - throw "Package Release Tests failed" - } - displayName: Run packaging release tests diff --git a/tools/releaseBuild/azureDevOps/templates/release-ValidatePackageNames.yml b/tools/releaseBuild/azureDevOps/templates/release-ValidatePackageNames.yml deleted file mode 100644 index 3a9aaa511f3..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/release-ValidatePackageNames.yml +++ /dev/null @@ -1,93 +0,0 @@ -steps: -- pwsh: | - Get-ChildItem ENV: | Out-String -width 9999 -Stream | write-Verbose -Verbose - displayName: Capture environment - -- template: release-SetReleaseTagAndContainerName.yml - -- pwsh: | - $name = "{0}_{1:x}" -f '$(releaseTag)', (Get-Date).Ticks - Write-Host $name - Write-Host "##vso[build.updatebuildnumber]$name" - displayName: Set Release Name - -- pwsh: | - Import-module '$(BUILD.SOURCESDIRECTORY)/build.psm1' - $azcopy = Find-AzCopy - Write-Verbose -Verbose "Found AzCopy: $azcopy" - - & $azcopy cp https://$(StorageAccount).blob.core.windows.net/$(AzureVersion)/* $(System.ArtifactsDirectory) --recursive - - displayName: Download Azure Artifacts - env: - AZCOPY_AUTO_LOGIN_TYPE: MSI - -- pwsh: | - Get-ChildItem $(System.ArtifactsDirectory)\* -recurse | Select-Object -ExpandProperty Name - displayName: Capture Artifact Listing - -- pwsh: | - $message = @() - Get-ChildItem $(System.ArtifactsDirectory)\* -recurse -filter *.rpm | ForEach-Object { - if($_.Name -notmatch 'powershell\-(preview-|lts-)?\d+\.\d+\.\d+(_[a-z]*\.\d+)?-1.(rh|cm).(x86_64|aarch64)\.rpm') - { - $messageInstance = "$($_.Name) is not a valid package name" - $message += $messageInstance - Write-Warning $messageInstance - } - } - if($message.count -gt 0){throw ($message | out-string)} - displayName: Validate RPM package names - -- pwsh: | - $message = @() - Get-ChildItem $(System.ArtifactsDirectory)\* -recurse -filter *.tar.gz | ForEach-Object { - if($_.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)') - { - $messageInstance = "$($_.Name) is not a valid package name" - $message += $messageInstance - Write-Warning $messageInstance - } - } - if($message.count -gt 0){throw ($message | out-string)} - displayName: Validate Tar.Gz Package Names - -- 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') - { - $messageInstance = "$($_.Name) is not a valid package name" - $message += $messageInstance - Write-Warning $messageInstance - } - } - if($message.count -gt 0){throw ($message | out-string)} - displayName: Validate PKG Package Names - -- 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}') - { - $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: | - $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') - { - $messageInstance = "$($_.Name) is not a valid package name" - $message += $messageInstance - Write-Warning $messageInstance - } - } - if($message.count -gt 0){throw ($message | out-string)} - displayName: Validate Deb Package Names diff --git a/tools/releaseBuild/azureDevOps/templates/release/approvalJob.yml b/tools/releaseBuild/azureDevOps/templates/release/approvalJob.yml deleted file mode 100644 index b34cc4c75b6..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/release/approvalJob.yml +++ /dev/null @@ -1,35 +0,0 @@ -parameters: - - name: displayName - type: string - - name: instructions - type: string - - name: jobName - type: string - default: approval - - name: timeoutInMinutes - type: number - # 2 days - default: 2880 - - name: onTimeout - type: string - default: 'reject' - values: - - resume - - reject - - name: dependsOnJob - type: string - default: '' - -jobs: - - job: ${{ parameters.jobName }} - dependsOn: ${{ parameters.dependsOnJob }} - displayName: ${{ parameters.displayName }} - pool: server - timeoutInMinutes: 4320 # job times out in 3 days - steps: - - task: ManualValidation@0 - displayName: ${{ parameters.displayName }} - timeoutInMinutes: ${{ parameters.timeoutInMinutes }} - inputs: - instructions: ${{ parameters.instructions }} - onTimeout: ${{ parameters.onTimeout }} diff --git a/tools/releaseBuild/azureDevOps/templates/shouldSign.yml b/tools/releaseBuild/azureDevOps/templates/shouldSign.yml deleted file mode 100644 index e3c38cb29d5..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/shouldSign.yml +++ /dev/null @@ -1,29 +0,0 @@ -steps: -- powershell: | - $shouldSign = $true - $authenticodeCert = 'CP-230012' - $msixCert = 'CP-230012' - - if($env:IS_DAILY -eq 'true') - { - $authenticodeCert = 'CP-460906' - } - - if($env:SKIP_SIGNING -eq 'Yes') - { - $shouldSign = $false - } - - $vstsCommandString = "vso[task.setvariable variable=SHOULD_SIGN]$($shouldSign.ToString().ToLowerInvariant())" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - - $vstsCommandString = "vso[task.setvariable variable=MSIX_CERT]$($msixCert)" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - - $vstsCommandString = "vso[task.setvariable variable=AUTHENTICODE_CERT]$($authenticodeCert)" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - - displayName: 'Set SHOULD_SIGN Variable' diff --git a/tools/releaseBuild/azureDevOps/templates/sign-build-file.yml b/tools/releaseBuild/azureDevOps/templates/sign-build-file.yml deleted file mode 100644 index a584e15e27c..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/sign-build-file.yml +++ /dev/null @@ -1,328 +0,0 @@ -steps: -- pwsh: | - $platform = '$(runtime)' -match '^linux' ? 'linux' : 'windows' - $vstsCommandString = "vso[task.setvariable variable=ArtifactPlatform]$platform" - Write-Host ("sending " + $vstsCommandString) - Write-Host "##$vstsCommandString" - displayName: Set artifact platform - -- task: DownloadPipelineArtifact@2 - inputs: - artifactName: '$(unsignedBuildArtifactContainer)' - itemPattern: '$(unsignedBuildArtifactName)' - -- pwsh: | - Get-ChildItem "$(Pipeline.Workspace)\*" -Recurse - displayName: 'Capture Downloaded Artifacts' - # Diagnostics is not critical it passes every time it runs - continueOnError: true - -- checkout: self - clean: true - path: $(repoFolder) - -- template: SetVersionVariables.yml - parameters: - ReleaseTagVar: $(ReleaseTagVar) - -- template: cloneToOfficialPath.yml - -- pwsh: | - $zipFileFilter = '$(unsignedBuildArtifactName)' - $zipFileFilter = $zipFileFilter.Replace('**/', '') - - Write-Verbose -Verbose -Message "zipFileFilter = $zipFileFilter" - - Write-Verbose -Verbose -Message "Looking for $(Pipeline.Workspace)\$(unsignedBuildArtifactName)" - - $zipFilePath = Get-ChildItem -Path '$(Pipeline.Workspace)\$(unsignedBuildArtifactName)' -recurse - - if (-not (Test-Path $zipFilePath)) - { - throw "zip file not found: $zipfilePath" - } - - if ($zipFilePath.Count -ne 1) { - Write-Verbose "zip filename" -verbose - $zipFilePath | Out-String | Write-Verbose -Verbose - throw 'multiple zip files found when 1 was expected' - } - - $expandedFolderName = [System.io.path]::GetFileNameWithoutExtension($zipfilePath) - $expandedFolderPath = Join-Path '$(Pipeline.Workspace)' 'expanded' $expandedFolderName - - Write-Verbose -Verbose -Message "Expaning $zipFilePath to $expandedFolderPath" - - New-Item -Path $expandedFolderPath -ItemType Directory - Expand-Archive -Path $zipFilePath -DestinationPath $expandedFolderPath - - if (-not (Test-Path $expandedFolderPath\pwsh.exe) ) { - throw 'zip did not expand as expected' - } - else { - $vstsCommandString = "vso[task.setvariable variable=BinPath]$expandedFolderPath" - Write-Host ("sending " + $vstsCommandString) - Write-Host "##$vstsCommandString" - } - - displayName: Expand zip packages - condition: eq(variables['ArtifactPlatform'], 'windows') - -- pwsh: | - $tarPackageName = '$(unsignedBuildArtifactName)' - - Write-Verbose -Verbose -Message "tarPackageName = $tarPackageName" - - $tarPackagePath = Join-Path '$(Pipeline.Workspace)' $tarPackageName - - Write-Verbose -Verbose -Message "Looking for: $tarPackagePath" - - $expandedPathFolderName = $tarPackageName -replace '.tar.gz', '' - $expandedFolderPath = Join-Path '$(Pipeline.Workspace)' 'expanded' $expandedPathFolderName - - if (-not (Test-Path $tarPackagePath)) - { - throw "tar file not found: $tarPackagePath" - } - - Write-Verbose -Verbose -Message "Expanding $tarPackagePath to $expandedFolderPath" - - New-Item -Path $expandedFolderPath -ItemType Directory - tar -xf $tarPackagePath -C $expandedFolderPath - - if (-not (Test-Path $expandedFolderPath/pwsh) ) { - throw 'tar.gz did not expand as expected' - } - else { - $vstsCommandString = "vso[task.setvariable variable=BinPath]$expandedFolderPath" - Write-Host ("sending " + $vstsCommandString) - Write-Host "##$vstsCommandString" - } - - Write-Verbose -Verbose "File permisions after expanding" - Get-ChildItem -Path "$expandedFolderPath/pwsh" | Select-Object -Property 'unixmode', 'size', 'name' - displayName: Expand tar.gz packages - condition: eq(variables['ArtifactPlatform'], 'linux') - -- template: insert-nuget-config-azfeed.yml - parameters: - repoRoot: $(PowerShellRoot) - -- pwsh: | - Set-Location $env:POWERSHELLROOT - import-module "$env:POWERSHELLROOT/build.psm1" - Sync-PSTags -AddRemoteIfMissing - displayName: SyncTags - condition: and(succeeded(), ne(variables['SkipBuild'], 'true')) - -- checkout: ComplianceRepo - clean: true - path: $(complianceRepoFolder) - -- template: shouldSign.yml - -- pwsh: | - $fullSymbolsFolder = '$(BinPath)' - Write-Verbose -Verbose "fullSymbolsFolder == $fullSymbolsFolder" - - Get-ChildItem -Recurse $fullSymbolsFolder | out-string | Write-Verbose -Verbose - - $filesToSignDirectory = "$(System.ArtifactsDirectory)\toBeSigned" - - if ((Test-Path -Path $filesToSignDirectory)) { - Remove-Item -Path $filesToSignDirectory -Recurse -Force - } - - $null = New-Item -ItemType Directory -Path $filesToSignDirectory -Force - - $signedFilesDirectory = "$(System.ArtifactsDirectory)\signed" - - if ((Test-Path -Path $signedFilesDirectory)) { - Remove-Item -Path $signedFilesDirectory -Recurse -Force - } - - $null = New-Item -ItemType Directory -Path $signedFilesDirectory -Force - - $itemsToCopyWithRecurse = @( - "$($fullSymbolsFolder)\*.ps1" - "$($fullSymbolsFolder)\Microsoft.PowerShell*.dll" - ) - - $itemsToCopy = @{ - "$($fullSymbolsFolder)\*.ps1" = "" - "$($fullSymbolsFolder)\Modules\Microsoft.PowerShell.Host\Microsoft.PowerShell.Host.psd1" = "Modules\Microsoft.PowerShell.Host" - "$($fullSymbolsFolder)\Modules\Microsoft.PowerShell.Management\Microsoft.PowerShell.Management.psd1" = "Modules\Microsoft.PowerShell.Management" - "$($fullSymbolsFolder)\Modules\Microsoft.PowerShell.Security\Microsoft.PowerShell.Security.psd1" = "Modules\Microsoft.PowerShell.Security" - "$($fullSymbolsFolder)\Modules\Microsoft.PowerShell.Utility\Microsoft.PowerShell.Utility.psd1" = "Modules\Microsoft.PowerShell.Utility" - "$($fullSymbolsFolder)\pwsh.dll" = "" - "$($fullSymbolsFolder)\System.Management.Automation.dll" = "" - } - - ## Windows only modules - - if('$(ArtifactPlatform)' -eq 'windows') { - $itemsToCopy += @{ - "$($fullSymbolsFolder)\pwsh.exe" = "" - "$($fullSymbolsFolder)\Microsoft.Management.Infrastructure.CimCmdlets.dll" = "" - "$($fullSymbolsFolder)\Microsoft.WSMan.*.dll" = "" - "$($fullSymbolsFolder)\Modules\CimCmdlets\CimCmdlets.psd1" = "Modules\CimCmdlets" - "$($fullSymbolsFolder)\Modules\Microsoft.PowerShell.Diagnostics\Diagnostics.format.ps1xml" = "Modules\Microsoft.PowerShell.Diagnostics" - "$($fullSymbolsFolder)\Modules\Microsoft.PowerShell.Diagnostics\Event.format.ps1xml" = "Modules\Microsoft.PowerShell.Diagnostics" - "$($fullSymbolsFolder)\Modules\Microsoft.PowerShell.Diagnostics\GetEvent.types.ps1xml" = "Modules\Microsoft.PowerShell.Diagnostics" - "$($fullSymbolsFolder)\Modules\Microsoft.PowerShell.Security\Security.types.ps1xml" = "Modules\Microsoft.PowerShell.Security" - "$($fullSymbolsFolder)\Modules\Microsoft.PowerShell.Diagnostics\Microsoft.PowerShell.Diagnostics.psd1" = "Modules\Microsoft.PowerShell.Diagnostics" - "$($fullSymbolsFolder)\Modules\Microsoft.WSMan.Management\Microsoft.WSMan.Management.psd1" = "Modules\Microsoft.WSMan.Management" - "$($fullSymbolsFolder)\Modules\Microsoft.WSMan.Management\WSMan.format.ps1xml" = "Modules\Microsoft.WSMan.Management" - "$($fullSymbolsFolder)\Modules\PSDiagnostics\PSDiagnostics.ps?1" = "Modules\PSDiagnostics" - } - } - else { - $itemsToCopy += @{ - "$($fullSymbolsFolder)\pwsh" = "" - } - } - - $itemsToExclude = @( - # This package is retrieved from https://www.github.com/powershell/MarkdownRender - "$($fullSymbolsFolder)\Microsoft.PowerShell.MarkdownRender.dll" - ) - - Write-Verbose -verbose "recusively copying $($itemsToCopyWithRecurse | out-string) to $filesToSignDirectory" - Copy-Item -Path $itemsToCopyWithRecurse -Destination $filesToSignDirectory -Recurse -verbose -exclude $itemsToExclude - - foreach($pattern in $itemsToCopy.Keys) { - $destinationFolder = Join-Path $filesToSignDirectory -ChildPath $itemsToCopy.$pattern - $null = New-Item -ItemType Directory -Path $destinationFolder -Force - Write-Verbose -verbose "copying $pattern to $destinationFolder" - Copy-Item -Path $pattern -Destination $destinationFolder -Recurse -verbose - } - displayName: 'Prepare files to be signed' - -- template: EsrpSign.yml@ComplianceRepo - parameters: - buildOutputPath: $(System.ArtifactsDirectory)\toBeSigned - signOutputPath: $(System.ArtifactsDirectory)\signed - certificateId: "$(AUTHENTICODE_CERT)" - pattern: | - **\*.dll - **\*.psd1 - **\*.psm1 - **\*.ps1xml - **\*.ps1 - **\*.exe - useMinimatch: true - shouldSign: $(SHOULD_SIGN) - displayName: Authenticode sign our binaries - -- pwsh: | - Import-Module $(PowerShellRoot)/build.psm1 -Force - Import-Module $(PowerShellRoot)/tools/packaging -Force - $signedFilesPath = '$(System.ArtifactsDirectory)\signed\' - $BuildPath = '$(BinPath)' - Write-Verbose -Verbose -Message "BuildPath: $BuildPath" - - Update-PSSignedBuildFolder -BuildPath $BuildPath -SignedFilesPath $SignedFilesPath - $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 - - Write-Verbose -verbose "to be signed:`r`n $($missingSignatures | Out-String)" - - $filesToSignDirectory = "$(System.ArtifactsDirectory)\thirdPartyToBeSigned" - if (Test-Path $filesToSignDirectory) { - Remove-Item -Path $filesToSignDirectory -Recurse -Force - } - - $null = New-Item -ItemType Directory -Path $filesToSignDirectory -Force -Verbose - - $signedFilesDirectory = "$(System.ArtifactsDirectory)\thirdPartySigned" - if (Test-Path $signedFilesDirectory) { - Remove-Item -Path $signedFilesDirectory -Recurse -Force - } - - $null = New-Item -ItemType Directory -Path $signedFilesDirectory -Force -Verbose - - $missingSignatures | ForEach-Object { - $pathWithoutLeaf = Split-Path $_ - $relativePath = $pathWithoutLeaf.replace($BuildPath,'') - Write-Verbose -Verbose -Message "relativePath: $relativePath" - $targetDirectory = Join-Path -Path $filesToSignDirectory -ChildPath $relativePath - Write-Verbose -Verbose -Message "targetDirectory: $targetDirectory" - if(!(Test-Path $targetDirectory)) - { - $null = New-Item -ItemType Directory -Path $targetDirectory -Force -Verbose - } - Copy-Item -Path $_ -Destination $targetDirectory - } - - displayName: Create ThirdParty Signing Folder - condition: and(succeeded(), eq(variables['SHOULD_SIGN'], 'true')) - -- template: EsrpSign.yml@ComplianceRepo - parameters: - buildOutputPath: $(System.ArtifactsDirectory)\thirdPartyToBeSigned - signOutputPath: $(System.ArtifactsDirectory)\thirdPartySigned - certificateId: "CP-231522" - pattern: | - **\*.dll - useMinimatch: true - shouldSign: $(SHOULD_SIGN) - displayName: Sign ThirdParty binaries - -- pwsh: | - Get-ChildItem '$(System.ArtifactsDirectory)\thirdPartySigned\*' - displayName: Capture ThirdParty Signed files - condition: and(succeeded(), eq(variables['SHOULD_SIGN'], 'true')) - -- pwsh: | - Import-Module '$(PowerShellRoot)/build.psm1' -Force - Import-Module '$(PowerShellRoot)/tools/packaging' -Force - $signedFilesPath = '$(System.ArtifactsDirectory)\thirdPartySigned' - $BuildPath = '$(BinPath)' - - Update-PSSignedBuildFolder -BuildPath $BuildPath -SignedFilesPath $SignedFilesPath - if ($env:BuildConfiguration -eq 'minSize') { - ## Remove XML files when making a min-size package. - Remove-Item "$BuildPath/*.xml" -Force - } - displayName: Merge ThirdParty signed files with Build - condition: and(succeeded(), eq(variables['SHOULD_SIGN'], 'true')) - -- pwsh: | - $uploadFolder = '$(BinPath)' - $containerName = '$(signedArtifactContainer)' - - Write-Verbose -Verbose "File permissions after signing" - Get-ChildItem $uploadFolder\pwsh | Select-Object -Property 'unixmode', 'size', 'name' - - $uploadTarFilePath = Join-Path '$(System.ArtifactsDirectory)' '$(signedBuildArtifactName)' - Write-Verbose -Verbose -Message "Creating tar.gz - $uploadTarFilePath" - tar -czvf $uploadTarFilePath -C $uploadFolder * - - Get-ChildItem '$(System.ArtifactsDirectory)' | Out-String | Write-Verbose -Verbose - - Write-Host "##vso[artifact.upload containerfolder=$containerName;artifactname=$containerName]$uploadTarFilePath" - displayName: Upload signed tar.gz files to artifacts - condition: eq(variables['ArtifactPlatform'], 'linux') - retryCountOnTaskFailure: 2 - - -- pwsh: | - $uploadFolder = '$(BinPath)' - $containerName = '$(signedArtifactContainer)' - - Get-ChildItem $uploadFolder -Recurse | Out-String | Write-Verbose -Verbose - - $uploadZipFilePath = Join-Path '$(System.ArtifactsDirectory)' 'PowerShell-$(Version)$(signedBuildArtifactName)' - Write-Verbose -Verbose -Message "Creating zip - $uploadZipFilePath" - Compress-Archive -Path $uploadFolder/* -DestinationPath $uploadZipFilePath -Verbose - - Get-ChildItem '$(System.ArtifactsDirectory)' | Out-String | Write-Verbose -Verbose - - Write-Host "##vso[artifact.upload containerfolder=$containerName;artifactname=$containerName]$uploadZipFilePath" - displayName: Upload signed zip files to artifacts - condition: eq(variables['ArtifactPlatform'], 'windows') - retryCountOnTaskFailure: 2 - - -- template: /tools/releaseBuild/azureDevOps/templates/step/finalize.yml diff --git a/tools/releaseBuild/azureDevOps/templates/signBuildFiles.yml b/tools/releaseBuild/azureDevOps/templates/signBuildFiles.yml deleted file mode 100644 index a7c7c640ce7..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/signBuildFiles.yml +++ /dev/null @@ -1,189 +0,0 @@ -parameters: - binLocation: '' - buildPrefixName: '' - addWindowsModules: 'false' - -steps: -- pwsh: | - $fullSymbolsFolder = Join-Path $(System.ArtifactsDirectory) "${{ parameters.binLocation }}" - - Write-Verbose -Verbose "fullSymbolsFolder == $fullSymbolsFolder" - - Get-ChildItem -Recurse $fullSymbolsFolder | out-string | Write-Verbose -Verbose - - $filesToSignDirectory = "$(System.ArtifactsDirectory)\toBeSigned" - - if ((Test-Path -Path $filesToSignDirectory)) { - Remove-Item -Path $filesToSignDirectory -Recurse -Force - } - - $null = New-Item -ItemType Directory -Path $filesToSignDirectory -Force - - $signedFilesDirectory = "$(System.ArtifactsDirectory)\signed" - - if ((Test-Path -Path $signedFilesDirectory)) { - Remove-Item -Path $signedFilesDirectory -Recurse -Force - } - - $null = New-Item -ItemType Directory -Path $signedFilesDirectory -Force - - $itemsToCopyWithRecurse = @( - "$($fullSymbolsFolder)\*.ps1" - "$($fullSymbolsFolder)\Microsoft.PowerShell*.dll" - ) - - $itemsToCopy = @{ - "$($fullSymbolsFolder)\*.ps1" = "" - "$($fullSymbolsFolder)\Modules\Microsoft.PowerShell.Host\Microsoft.PowerShell.Host.psd1" = "Modules\Microsoft.PowerShell.Host" - "$($fullSymbolsFolder)\Modules\Microsoft.PowerShell.Management\Microsoft.PowerShell.Management.psd1" = "Modules\Microsoft.PowerShell.Management" - "$($fullSymbolsFolder)\Modules\Microsoft.PowerShell.Security\Microsoft.PowerShell.Security.psd1" = "Modules\Microsoft.PowerShell.Security" - "$($fullSymbolsFolder)\Modules\Microsoft.PowerShell.Utility\Microsoft.PowerShell.Utility.psd1" = "Modules\Microsoft.PowerShell.Utility" - "$($fullSymbolsFolder)\pwsh.dll" = "" - "$($fullSymbolsFolder)\System.Management.Automation.dll" = "" - } - - ## Windows only modules - - if('${{ parameters.addWindowsModules }}' -ne 'false') { - $itemsToCopy += @{ - "$($fullSymbolsFolder)\pwsh.exe" = "" - "$($fullSymbolsFolder)\Microsoft.Management.Infrastructure.CimCmdlets.dll" = "" - "$($fullSymbolsFolder)\Microsoft.WSMan.*.dll" = "" - "$($fullSymbolsFolder)\Modules\CimCmdlets\CimCmdlets.psd1" = "Modules\CimCmdlets" - "$($fullSymbolsFolder)\Modules\Microsoft.PowerShell.Diagnostics\Diagnostics.format.ps1xml" = "Modules\Microsoft.PowerShell.Diagnostics" - "$($fullSymbolsFolder)\Modules\Microsoft.PowerShell.Diagnostics\Event.format.ps1xml" = "Modules\Microsoft.PowerShell.Diagnostics" - "$($fullSymbolsFolder)\Modules\Microsoft.PowerShell.Diagnostics\GetEvent.types.ps1xml" = "Modules\Microsoft.PowerShell.Diagnostics" - "$($fullSymbolsFolder)\Modules\Microsoft.PowerShell.Security\Security.types.ps1xml" = "Modules\Microsoft.PowerShell.Security" - "$($fullSymbolsFolder)\Modules\Microsoft.PowerShell.Diagnostics\Microsoft.PowerShell.Diagnostics.psd1" = "Modules\Microsoft.PowerShell.Diagnostics" - "$($fullSymbolsFolder)\Modules\Microsoft.WSMan.Management\Microsoft.WSMan.Management.psd1" = "Modules\Microsoft.WSMan.Management" - "$($fullSymbolsFolder)\Modules\Microsoft.WSMan.Management\WSMan.format.ps1xml" = "Modules\Microsoft.WSMan.Management" - "$($fullSymbolsFolder)\Modules\PSDiagnostics\PSDiagnostics.ps?1" = "Modules\PSDiagnostics" - } - } - else { - $itemsToCopy += @{ - "$($fullSymbolsFolder)\pwsh" = "" - } - } - - $itemsToExclude = @( - # This package is retrieved from https://www.github.com/powershell/MarkdownRender - "$($fullSymbolsFolder)\Microsoft.PowerShell.MarkdownRender.dll" - ) - - Write-Verbose -verbose "recusively copying $($itemsToCopyWithRecurse | out-string) to $filesToSignDirectory" - Copy-Item -Path $itemsToCopyWithRecurse -Destination $filesToSignDirectory -Recurse -verbose -exclude $itemsToExclude - - foreach($pattern in $itemsToCopy.Keys) { - $destinationFolder = Join-Path $filesToSignDirectory -ChildPath $itemsToCopy.$pattern - $null = New-Item -ItemType Directory -Path $destinationFolder -Force - Write-Verbose -verbose "copying $pattern to $destinationFolder" - Copy-Item -Path $pattern -Destination $destinationFolder -Recurse -verbose - } - displayName: '${{ parameters.buildPrefixName }} - Prepare files to be signed' - -- template: EsrpSign.yml@ComplianceRepo - parameters: - buildOutputPath: $(System.ArtifactsDirectory)\toBeSigned - signOutputPath: $(System.ArtifactsDirectory)\signed - certificateId: "$(AUTHENTICODE_CERT)" - pattern: | - **\*.dll - **\*.psd1 - **\*.psm1 - **\*.ps1xml - **\*.ps1 - **\*.exe - useMinimatch: true - shouldSign: $(SHOULD_SIGN) - displayName: ${{ parameters.buildPrefixName }} - Authenticode - -- pwsh: | - Import-Module $(PowerShellRoot)/build.psm1 -Force - Import-Module $(PowerShellRoot)/tools/packaging -Force - $signedFilesPath = '$(System.ArtifactsDirectory)\signed\' - $BuildPath = Join-Path $(System.ArtifactsDirectory) '${{ parameters.binLocation }}' - Write-Verbose -Verbose -Message "BuildPath: $BuildPath" - - Update-PSSignedBuildFolder -BuildPath $BuildPath -SignedFilesPath $SignedFilesPath - $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 - - Write-Verbose -verbose "to be signed:`r`n $($missingSignatures | Out-String)" - - $filesToSignDirectory = "$(System.ArtifactsDirectory)\thirdPartyToBeSigned" - if (Test-Path $filesToSignDirectory) { - Remove-Item -Path $filesToSignDirectory -Recurse -Force - } - - $null = New-Item -ItemType Directory -Path $filesToSignDirectory -Force -Verbose - - $signedFilesDirectory = "$(System.ArtifactsDirectory)\thirdPartySigned" - if (Test-Path $signedFilesDirectory) { - Remove-Item -Path $signedFilesDirectory -Recurse -Force - } - - $null = New-Item -ItemType Directory -Path $signedFilesDirectory -Force -Verbose - - $missingSignatures | ForEach-Object { - $pathWithoutLeaf = Split-Path $_ - $relativePath = $pathWithoutLeaf.replace($BuildPath,'') - Write-Verbose -Verbose -Message "relativePath: $relativePath" - $targetDirectory = Join-Path -Path $filesToSignDirectory -ChildPath $relativePath - Write-Verbose -Verbose -Message "targetDirectory: $targetDirectory" - if(!(Test-Path $targetDirectory)) - { - $null = New-Item -ItemType Directory -Path $targetDirectory -Force -Verbose - } - Copy-Item -Path $_ -Destination $targetDirectory - } - - displayName: ${{ parameters.buildPrefixName }} - Create ThirdParty Signing Folder - condition: and(succeeded(), eq(variables['SHOULD_SIGN'], 'true')) - -- template: EsrpSign.yml@ComplianceRepo - parameters: - buildOutputPath: $(System.ArtifactsDirectory)\thirdPartyToBeSigned - signOutputPath: $(System.ArtifactsDirectory)\thirdPartySigned - certificateId: "CP-231522" - pattern: | - **\*.dll - useMinimatch: true - shouldSign: $(SHOULD_SIGN) - displayName: Sign ThirdParty binaries - -- pwsh: | - Get-ChildItem '$(System.ArtifactsDirectory)\thirdPartySigned\*' - displayName: ${{ parameters.buildPrefixName }} - Capture ThirdParty Signed files - condition: and(succeeded(), eq(variables['SHOULD_SIGN'], 'true')) - -- pwsh: | - Import-Module $(PowerShellRoot)/build.psm1 -Force - Import-Module $(PowerShellRoot)/tools/packaging -Force - $signedFilesPath = '$(System.ArtifactsDirectory)\thirdPartySigned' - $BuildPath = Join-Path $(System.ArtifactsDirectory) '${{ parameters.binLocation }}' - - Update-PSSignedBuildFolder -BuildPath $BuildPath -SignedFilesPath $SignedFilesPath - if ($env:BuildConfiguration -eq 'minSize') { - ## Remove XML files when making a min-size package. - Remove-Item "$BuildPath/*.xml" -Force - } - displayName: ${{ parameters.buildPrefixName }} - Merge ThirdParty signed files with Build - condition: and(succeeded(), eq(variables['SHOULD_SIGN'], 'true')) - -- pwsh: | - $uploadFolder = '$(System.ArtifactsDirectory)/${{ parameters.binLocation }}' - $containerName = 'authenticode-signed' - - Write-Verbose -Verbose "File permissions after signing" - Get-ChildItem $uploadFolder\pwsh | Select-Object -Property 'unixmode', 'size', 'name' - - $uploadTarFilePath = '$(System.ArtifactsDirectory)/${{ parameters.binLocation }}.tar.gz' - Write-Verbose -Verbose -Message "Creating tar.gz - $uploadTarFilePath" - tar -czvf $uploadTarFilePath -C $uploadFolder * - - Write-Host "##vso[artifact.upload containerfolder=$containerName;artifactname=$containerName]$uploadTarFilePath" - displayName: ${{ parameters.buildPrefixName }} - Upload signed files to artifacts - retryCountOnTaskFailure: 2 - diff --git a/tools/releaseBuild/azureDevOps/templates/step/finalize.yml b/tools/releaseBuild/azureDevOps/templates/step/finalize.yml deleted file mode 100644 index 72a677fec9a..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/step/finalize.yml +++ /dev/null @@ -1,5 +0,0 @@ -steps: - - pwsh: | - throw "Jobs with an Issue will not work for release. Please fix the issue and try again." - displayName: Check for SucceededWithIssues - condition: eq(variables['Agent.JobStatus'],'SucceededWithIssues') diff --git a/tools/releaseBuild/azureDevOps/templates/testartifacts.yml b/tools/releaseBuild/azureDevOps/templates/testartifacts.yml deleted file mode 100644 index 43c09236da9..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/testartifacts.yml +++ /dev/null @@ -1,126 +0,0 @@ -jobs: -- job: build_testartifacts_win - variables: - - name: runCodesignValidationInjection - value: false - - name: NugetSecurityAnalysisWarningLevel - value: none - - group: DotNetPrivateBuildAccess - displayName: Build windows test artifacts - condition: succeeded() - pool: - name: PowerShell1ES - demands: - - ImageOverride -equals PSMMS2019-Secure - steps: - - checkout: self - clean: true - - - template: /tools/releaseBuild/azureDevOps/templates/insert-nuget-config-azfeed.yml - parameters: - repoRoot: $(Build.SourcesDirectory) - - - pwsh: | - Import-Module ./build.psm1 - Start-PSBootstrap - displayName: Bootstrap - env: - __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) - - - pwsh: | - Import-Module ./build.psm1 - - function BuildTestPackage([string] $runtime) - { - Write-Verbose -Verbose "Starting to build package for $runtime" - - New-TestPackage -Destination $(System.ArtifactsDirectory) -Runtime $runtime - - if (-not (Test-Path $(System.ArtifactsDirectory)/TestPackage.zip)) - { - throw "Test Package was not found at: $(System.ArtifactsDirectory)" - } - - switch ($runtime) - { - win7-x64 { $packageName = "TestPackage-win-x64.zip" } - win7-x86 { $packageName = "TestPackage-win-x86.zip" } - win-arm64 { $packageName = "TestPackage-win-arm64.zip" } - } - - Rename-Item $(System.ArtifactsDirectory)/TestPackage.zip $packageName - Write-Host "##vso[artifact.upload containerfolder=testArtifacts;artifactname=testArtifacts]$(System.ArtifactsDirectory)/$packageName" - } - - BuildTestPackage -runtime win7-x64 - BuildTestPackage -runtime win7-x86 - BuildTestPackage -runtime win-arm64 - - displayName: Build test package and upload - retryCountOnTaskFailure: 1 - -- job: build_testartifacts_nonwin - variables: - - name: runCodesignValidationInjection - value: false - - name: NugetSecurityAnalysisWarningLevel - value: none - - group: DotNetPrivateBuildAccess - displayName: Build non-windows test artifacts - condition: succeeded() - pool: - name: PowerShell1ES - demands: - - ImageOverride -equals PSMMSUbuntu20.04-Secure - steps: - - checkout: self - clean: true - - - template: /tools/releaseBuild/azureDevOps/templates/insert-nuget-config-azfeed.yml - parameters: - repoRoot: $(Build.SourcesDirectory) - - - pwsh: | - Import-Module ./build.psm1 - Start-PSBootstrap - displayName: Bootstrap - env: - __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) - - - pwsh: | - Import-Module ./build.psm1 - - function BuildTestPackage([string] $runtime) - { - Write-Verbose -Verbose "Starting to build package for $runtime" - - New-TestPackage -Destination $(System.ArtifactsDirectory) -Runtime $runtime - - if (-not (Test-Path $(System.ArtifactsDirectory)/TestPackage.zip)) - { - throw "Test Package was not found at: $(System.ArtifactsDirectory)" - } - - switch ($runtime) - { - linux-x64 { $packageName = "TestPackage-linux-x64.zip" } - linux-arm { $packageName = "TestPackage-linux-arm.zip" } - linux-arm64 { $packageName = "TestPackage-linux-arm64.zip" } - osx-x64 { $packageName = "TestPackage-macOS.zip" } - linux-musl-x64 { $packageName = "TestPackage-alpine-x64.zip"} - } - - Rename-Item $(System.ArtifactsDirectory)/TestPackage.zip $packageName - Write-Host "##vso[artifact.upload containerfolder=testArtifacts;artifactname=testArtifacts]$(System.ArtifactsDirectory)/$packageName" - } - - BuildTestPackage -runtime linux-x64 - BuildTestPackage -runtime linux-arm - BuildTestPackage -runtime linux-arm64 - BuildTestPackage -runtime osx-x64 - BuildTestPackage -runtime linux-musl-x64 - - displayName: Build test package and upload - retryCountOnTaskFailure: 1 - - - template: /tools/releaseBuild/azureDevOps/templates/step/finalize.yml diff --git a/tools/releaseBuild/azureDevOps/templates/upload-final-results.yml b/tools/releaseBuild/azureDevOps/templates/upload-final-results.yml deleted file mode 100644 index 596b61fb6ed..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/upload-final-results.yml +++ /dev/null @@ -1,17 +0,0 @@ -parameters: - artifactPath: - artifactFilter: '*' - condition: succeeded() - artifactName: finalResults - -steps: - - powershell: | - Get-ChildItem -Path '${{ parameters.artifactPath }}' -Recurse -File -filter '${{ parameters.artifactFilter }}' -ErrorAction SilentlyContinue | - Select-Object -ExpandProperty FullName | - ForEach-Object { - Write-Host "##vso[artifact.upload containerfolder=${{ parameters.artifactName }};artifactname=${{ parameters.artifactName }}]$_" - } - displayName: Upload ${{ parameters.artifactName }} Artifacts ${{ parameters.artifactFilter }} from ${{ parameters.artifactPath }} - condition: ${{ parameters.condition }} - retryCountOnTaskFailure: 2 - diff --git a/tools/releaseBuild/azureDevOps/templates/upload.yml b/tools/releaseBuild/azureDevOps/templates/upload.yml deleted file mode 100644 index c745a02c2a4..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/upload.yml +++ /dev/null @@ -1,83 +0,0 @@ -parameters: - architecture: x86 - version: 6.2.0 - msi: yes - msix: yes - pdb: no - -steps: -- template: upload-final-results.yml - parameters: - artifactPath: $(System.ArtifactsDirectory)\signed - artifactFilter: PowerShell-${{ parameters.version }}-win-${{ parameters.architecture }}*.zip - -- task: AzureFileCopy@4 - displayName: 'upload signed zip to Azure - ${{ parameters.architecture }}' - inputs: - SourcePath: '$(System.ArtifactsDirectory)\signed\PowerShell-${{ parameters.version }}-win-${{ parameters.architecture }}.zip' - azureSubscription: '$(AzureFileCopySubscription)' - Destination: AzureBlob - storage: '$(StorageAccount)' - ContainerName: '$(AzureVersion)' - resourceGroup: '$(StorageResourceGroup)' - condition: succeeded() - retryCountOnTaskFailure: 2 - -- task: AzureFileCopy@4 - displayName: 'upload signed min-size package (for Guest Config) to Azure - ${{ parameters.architecture }}' - inputs: - SourcePath: '$(System.ArtifactsDirectory)\signed\PowerShell-${{ parameters.version }}-win-${{ parameters.architecture }}-gc.zip' - azureSubscription: '$(AzureFileCopySubscription)' - Destination: AzureBlob - storage: '$(StorageAccount)' - ContainerName: '$(AzureVersion)-gc' - resourceGroup: '$(StorageResourceGroup)' - condition: and(eq('${{ parameters.architecture }}', 'x64'), succeeded()) - retryCountOnTaskFailure: 2 - -- template: upload-final-results.yml - parameters: - artifactPath: $(System.ArtifactsDirectory)\signedPackages - artifactFilter: PowerShell-${{ parameters.version }}-win-${{ parameters.architecture }}.exe - condition: and(succeeded(), eq('${{ parameters.msi }}', 'yes')) - -- task: AzureFileCopy@4 - displayName: 'upload signed exe to Azure - ${{ parameters.architecture }}' - inputs: - SourcePath: '$(System.ArtifactsDirectory)\signedPackages\PowerShell-${{ parameters.version }}-win-${{ parameters.architecture }}.exe' - azureSubscription: '$(AzureFileCopySubscription)' - Destination: AzureBlob - storage: '$(StorageAccount)' - ContainerName: '$(AzureVersion)-private' - resourceGroup: '$(StorageResourceGroup)' - condition: and(succeeded(), eq('${{ parameters.msi }}', 'yes')) - retryCountOnTaskFailure: 2 - -# Disable upload task as the symbols package is not currently used and we want to avoid publishing this in releases -#- task: AzureFileCopy@4 -# displayName: 'upload pbd zip to Azure - ${{ parameters.architecture }}' -# inputs: -# SourcePath: '$(System.ArtifactsDirectory)\signed\PowerShell-Symbols-${{ parameters.version }}-win-${{ parameters.architecture }}.zip' -# azureSubscription: '$(AzureFileCopySubscription)' -# Destination: AzureBlob -# storage: '$(StorageAccount)' -# ContainerName: '$(AzureVersion)' -# condition: and(succeeded(), eq('${{ parameters.pdb }}', 'yes')) - -- template: upload-final-results.yml - parameters: - artifactPath: $(Build.StagingDirectory)\signedPackages - artifactFilter: PowerShell-${{ parameters.version }}-win-${{ parameters.architecture }}.msix - condition: and(succeeded(), eq('${{ parameters.msix }}', 'yes')) - -- task: AzureFileCopy@4 - displayName: 'upload signed msix to Azure - ${{ parameters.architecture }}' - inputs: - SourcePath: '$(Build.StagingDirectory)\signedPackages\PowerShell-${{ parameters.version }}-win-${{ parameters.architecture }}.msix' - azureSubscription: '$(AzureFileCopySubscription)' - Destination: AzureBlob - storage: '$(StorageAccount)' - ContainerName: '$(AzureVersion)-private' - resourceGroup: '$(StorageResourceGroup)' - condition: and(succeeded(), eq('${{ parameters.msix }}', 'yes'), eq(variables['SHOULD_SIGN'], 'true')) - retryCountOnTaskFailure: 2 diff --git a/tools/releaseBuild/azureDevOps/templates/vpackReleaseJob.yml b/tools/releaseBuild/azureDevOps/templates/vpackReleaseJob.yml deleted file mode 100644 index 25517dae9c5..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/vpackReleaseJob.yml +++ /dev/null @@ -1,113 +0,0 @@ -parameters: - architecture: x64 - -jobs: -- job: vpack_${{ parameters.architecture }} - variables: - - group: vPack - - group: ReleasePipelineSecrets - - displayName: Build and Publish VPack - ${{ parameters.architecture }} - condition: succeeded() - pool: - name: PowerShell1ES - demands: - - ImageOverride -equals PSMMS2019-Secure - steps: - - checkout: self - clean: true - - - task: UseDotNet@2 - displayName: 'Use .NET Core sdk' - inputs: - packageType: sdk - version: 3.1.x - installationPath: $(Agent.ToolsDirectory)/dotnet - - - template: ./SetVersionVariables.yml - parameters: - ReleaseTagVar: $(ReleaseTagVar) - - - pwsh: | - Import-module '$(BUILD.SOURCESDIRECTORY)/build.psm1' - Install-AzCopy - displayName: Install AzCopy - retryCountOnTaskFailure: 2 - - - pwsh: | - Import-module '$(BUILD.SOURCESDIRECTORY)/build.psm1' - $azcopy = Find-AzCopy - Write-Verbose -Verbose "Found AzCopy: $azcopy" - - Write-Host "running: $azcopy cp https://$(StorageAccount).blob.core.windows.net/$(AzureVersion)/PowerShell-$(Version)-win-${{ parameters.architecture }}.zip $(System.ArtifactsDirectory)" - - & $azcopy cp https://$(StorageAccount).blob.core.windows.net/$(AzureVersion)/PowerShell-$(Version)-win-${{ parameters.architecture }}.zip $(System.ArtifactsDirectory) - displayName: 'Download Azure Artifacts' - retryCountOnTaskFailure: 2 - env: - AZCOPY_AUTO_LOGIN_TYPE: MSI - - - pwsh: 'Get-ChildItem $(System.ArtifactsDirectory)\* -recurse | Select-Object -ExpandProperty Name' - displayName: 'Capture Artifact Listing' - - - 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}') - { - $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 $(System.ArtifactsDirectory)\* -recurse -include *.zip, *.msi | ForEach-Object { - if($_.Name -match 'PowerShell-\d+\.\d+\.\d+\-([a-z]*.\d+\-)?win\-(${{ parameters.architecture }})\.(zip){1}') - { - $destDir = "$(System.ArtifactsDirectory)\vpack${{ parameters.architecture }}" - $null = new-item -ItemType Directory -Path $destDir - Expand-Archive -Path $_.FullName -DestinationPath $destDir - $vstsCommandString = "vso[task.setvariable variable=vpackDir]$destDir" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - } - } - displayName: 'Extract Zip' - - - pwsh: | - $vpackVersion = '$(version)' - - if('$(VPackPublishOverride)' -ne '' -and '$(VPackPublishOverride)' -ne 'None' ) - { - Write-Host "Using VPackPublishOverride varabile" - $vpackVersion = '$(VPackPublishOverride)' - } - - $vstsCommandString = "vso[task.setvariable variable=vpackVersion]$vpackVersion" - Write-Host "sending " + $vstsCommandString - Write-Host "##$vstsCommandString" - displayName: 'Set vpackVersion' - - - pwsh: | - Get-ChildItem -Path env: | Out-String -width 9999 -Stream | write-Verbose -Verbose - displayName: Capture Environment - condition: succeededOrFailed() - - - task: PkgESVPack@12 - displayName: 'Package ES - VPack ' - inputs: - sourceDirectory: '$(vpackDir)' - description: PowerShell ${{ parameters.architecture }} $(version) - pushPkgName: 'PowerShell.${{ parameters.architecture }}' - configurations: Release - platforms: x64 - target: '$(System.ArtifactsDirectory)' - owner: tplunk - provData: true - version: '$(vpackVersion)' - vpackToken: $(vPackPat) - condition: and(succeeded(), eq(variables['Build.Reason'], 'Manual')) diff --git a/tools/releaseBuild/azureDevOps/templates/windows-component-governance.yml b/tools/releaseBuild/azureDevOps/templates/windows-component-governance.yml deleted file mode 100644 index 53947655d90..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/windows-component-governance.yml +++ /dev/null @@ -1,71 +0,0 @@ - -jobs: -- job: ComponentRegistrationJob - variables: - - name: runCodesignValidationInjection - value: false - - name: NugetSecurityAnalysisWarningLevel - value: none - displayName: Component Registration - condition: succeeded() - - pool: - name: PowerShell1ES - demands: - - ImageOverride -equals PSMMS2019-Secure - - steps: - - checkout: self - clean: true - - - template: SetVersionVariables.yml - parameters: - ReleaseTagVar: $(ReleaseTagVar) - - - powershell: | - docker container prune --force - docker container ls --all --format '{{ json .ID }}' | ConvertFrom-Json | ForEach-Object {docker container rm --force --volumes $_} - displayName: 'Remove all containers' - # Cleanup is not critical it passes every time it runs - continueOnError: true - - - powershell: | - docker image ls --format '{{ json .}}'|ConvertFrom-Json| ForEach-Object { - if($_.tag -eq '<none>') - { - $formatString = 'yyyy-MM-dd HH:mm:ss zz00' - $createdAtString = $_.CreatedAt.substring(0,$_.CreatedAt.Length -4) - $createdAt = [DateTime]::ParseExact($createdAtString, $formatString,[System.Globalization.CultureInfo]::InvariantCulture) - if($createdAt -lt (Get-Date).adddays(-1)) - { - docker image rm $_.ID - } - } - } - exit 0 - displayName: 'Remove old images' - # Cleanup is not critical it passes every time it runs - continueOnError: true - - - powershell: | - Write-verbose "--docker info---" -verbose - docker info - Write-verbose "--docker image ls---" -verbose - docker image ls - Write-verbose "--docker container ls --all---" -verbose - docker container ls --all - displayName: 'Capture Docker Info' - # Diagnostics is not critical it passes every time it runs - continueOnError: true - - - template: insert-nuget-config-azfeed.yml - - - powershell: | - ./tools/releaseBuild/vstsbuild.ps1 -ReleaseTag $(ReleaseTagVar) -Name win-x64-component-registration - displayName: 'Build Windows Universal - Component Registration' - - - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 - displayName: 'Component Detection' - inputs: - sourceScanPath: '$(componentregistration)' - snapshotForceEnabled: true diff --git a/tools/releaseBuild/azureDevOps/templates/windows-hosted-build.yml b/tools/releaseBuild/azureDevOps/templates/windows-hosted-build.yml deleted file mode 100644 index 4b36f6f396e..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/windows-hosted-build.yml +++ /dev/null @@ -1,84 +0,0 @@ -parameters: - - name: BuildConfiguration - default: release - - name: BuildPlatform - default: any cpu - - name: Architecture - default: x64 - - name: parentJob - default: '' - -jobs: -- job: build_windows_${{ parameters.Architecture }}_${{ parameters.BuildConfiguration }} - displayName: Build Windows - ${{ parameters.Architecture }} ${{ parameters.BuildConfiguration }} - condition: succeeded() - dependsOn: ${{ parameters.parentJob }} - pool: - name: $(windowsPool) - demands: - - ImageOverride -equals PSMMS2019-Secure - variables: - - name: runCodesignValidationInjection - value: false - - name: NugetSecurityAnalysisWarningLevel - value: none - - name: BuildConfiguration - value: ${{ parameters.BuildConfiguration }} - - name: BuildPlatform - value: ${{ parameters.BuildPlatform }} - - name: Architecture - value: ${{ parameters.Architecture }} - - name: DOTNET_SKIP_FIRST_TIME_EXPERIENCE - value: 1 - - group: DotNetPrivateBuildAccess - - steps: - - - checkout: self - clean: true - - - template: SetVersionVariables.yml - parameters: - ReleaseTagVar: $(ReleaseTagVar) - - - template: cloneToOfficialPath.yml - - - template: /tools/releaseBuild/azureDevOps/templates/insert-nuget-config-azfeed.yml - parameters: - repoRoot: $(PowerShellRoot) - - - pwsh: | - - $runtime = switch ($env:Architecture) - { - "x64" { "win7-x64" } - "x86" { "win7-x86" } - "arm64" { "win-arm64" } - "fxdependent" { "fxdependent" } - "fxdependentWinDesktop" { "fxdependent-win-desktop" } - } - - $params = @{} - if ($env:BuildConfiguration -eq 'minSize') { - $params['ForMinimalSize'] = $true - } - - tools/releaseBuild/Images/microsoft_powershell_windowsservercore/PowerShellPackage.ps1 -location '$(PowerShellRoot)' -destination '$(Build.ArtifactStagingDirectory)/Symbols_$(Architecture)' -Runtime $runtime -ReleaseTag '$(ReleaseTagVar)' -Symbols @params - displayName: 'Build Windows Universal - $(Architecture)-$(BuildConfiguration) Symbols zip' - env: - __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) - - - pwsh: | - $packageName = (Get-ChildItem '$(Build.ArtifactStagingDirectory)\Symbols_$(Architecture)').FullName - $vstsCommandString = "vso[artifact.upload containerfolder=results;artifactname=results]$packageName" - Write-Host ("sending " + $vstsCommandString) - Write-Host "##$vstsCommandString" - displayName: Upload symbols package - - - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 - displayName: 'Component Detection' - inputs: - sourceScanPath: '$(PowerShellRoot)\tools' - snapshotForceEnabled: true - - - template: /tools/releaseBuild/azureDevOps/templates/step/finalize.yml diff --git a/tools/releaseBuild/azureDevOps/templates/windows-package-signing.yml b/tools/releaseBuild/azureDevOps/templates/windows-package-signing.yml deleted file mode 100644 index 75153ce0592..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/windows-package-signing.yml +++ /dev/null @@ -1,132 +0,0 @@ -parameters: - parentJobs: [] - -jobs: -- job: WinPackageSigningJob - displayName: Windows Package signing and upload - dependsOn: - ${{ parameters.parentJobs }} - condition: succeeded() - pool: - name: $(windowsPool) - demands: - - ImageOverride -equals PSMMS2019-Secure - variables: - - name: DOTNET_SKIP_FIRST_TIME_EXPERIENCE - value: 1 - - group: ESRP - - name: repoFolder - value: PowerShell - - name: repoRoot - value: $(Agent.BuildDirectory)\$(repoFolder) - - name: complianceRepoFolder - value: compliance - - steps: - - checkout: self - clean: true - path: $(repoFolder) - - - checkout: ComplianceRepo - clean: true - path: $(complianceRepoFolder) - - - template: SetVersionVariables.yml - parameters: - ReleaseTagVar: $(ReleaseTagVar) - - - template: shouldSign.yml - - - task: DownloadBuildArtifacts@0 - displayName: 'Download artifacts' - inputs: - buildType: current - downloadType: single - artifactName: signed - downloadPath: '$(System.ArtifactsDirectory)' - - - powershell: | - dir "$(System.ArtifactsDirectory)\*" -Recurse - displayName: 'Capture Downloaded Artifacts' - # Diagnostics is not critical it passes every time it runs - continueOnError: true - - - template: EsrpSign.yml@ComplianceRepo - parameters: - buildOutputPath: $(System.ArtifactsDirectory)\signed - signOutputPath: $(Build.StagingDirectory)\signedPackages - certificateId: $(MSIX_CERT) - pattern: | - **\*.msix - useMinimatch: true - shouldSign: $(SHOULD_SIGN) - displayName: Sign msix - - - template: EsrpSign.yml@ComplianceRepo - parameters: - buildOutputPath: $(System.ArtifactsDirectory)\signed - signOutputPath: $(Build.StagingDirectory)\signedPackages - certificateId: $(AUTHENTICODE_CERT) - pattern: | - **\*.exe - useMinimatch: true - shouldSign: $(SHOULD_SIGN) - displayName: Sign exe - - - powershell: | - new-item -itemtype Directory -path '$(Build.StagingDirectory)\signedPackages' - Get-ChildItem "$(System.ArtifactsDirectory)\signed\PowerShell-$(Version)-win-*.msi*" | copy-item -Destination '$(Build.StagingDirectory)\signedPackages' - displayName: 'Fake msi* Signing' - condition: and(succeeded(), ne(variables['SHOULD_SIGN'], 'true')) - - - pwsh: | - Get-ChildItem "$(System.ArtifactsDirectory)\signed\PowerShell-$(Version)-win-*.exe" | copy-item -Destination '$(Build.StagingDirectory)\signedPackages' - displayName: 'Fake exe Signing' - condition: and(succeeded(), ne(variables['SHOULD_SIGN'], 'true')) - - - template: upload.yml - parameters: - architecture: x86 - version: $(version) - - - template: upload.yml - parameters: - architecture: x64 - version: $(version) - pdb: yes - - - template: upload.yml - parameters: - architecture: arm64 - version: $(version) - msi: yes - - - template: upload.yml - parameters: - architecture: fxdependent - version: $(version) - msi: no - msix: no - - - template: upload.yml - parameters: - architecture: fxdependentWinDesktop - version: $(version) - msi: no - msix: no - - - template: EsrpScan.yml@ComplianceRepo - parameters: - scanPath: $(Build.StagingDirectory) - pattern: | - **\*.msix - **\*.msi - **\*.zip - - - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 - displayName: 'Component Detection' - inputs: - sourceScanPath: '$(repoRoot)\tools' - snapshotForceEnabled: true - - - template: /tools/releaseBuild/azureDevOps/templates/step/finalize.yml diff --git a/tools/releaseBuild/azureDevOps/templates/windows-packaging.yml b/tools/releaseBuild/azureDevOps/templates/windows-packaging.yml deleted file mode 100644 index 915db9301ac..00000000000 --- a/tools/releaseBuild/azureDevOps/templates/windows-packaging.yml +++ /dev/null @@ -1,369 +0,0 @@ -parameters: - - name: BuildConfiguration - default: release - - name: BuildPlatform - default: any cpu - - name: Architecture - default: x64 - - name: parentJob - default: '' - -jobs: -- job: sign_windows_${{ parameters.Architecture }}_${{ parameters.BuildConfiguration }} - displayName: Package Windows - ${{ parameters.Architecture }} ${{ parameters.BuildConfiguration }} - condition: succeeded() - pool: - name: $(windowsPool) - demands: - - ImageOverride -equals PSMMS2019-Secure - variables: - - name: BuildConfiguration - value: ${{ parameters.BuildConfiguration }} - - name: BuildPlatform - value: ${{ parameters.BuildPlatform }} - - name: Architecture - value: ${{ parameters.Architecture }} - - name: DOTNET_SKIP_FIRST_TIME_EXPERIENCE - value: 1 - - group: ESRP - - group: DotNetPrivateBuildAccess - - steps: - - - checkout: self - clean: true - - - checkout: ComplianceRepo - clean: true - - - template: SetVersionVariables.yml - parameters: - ReleaseTagVar: $(ReleaseTagVar) - - - template: shouldSign.yml - - - pwsh: | - $pkgFilter = '$(Architecture)' - if ($env:BuildConfiguration -eq 'minSize') { $pkgFilter += '-gc' } - - $vstsCommandString = "vso[task.setvariable variable=PkgFilter]$pkgFilter" - Write-Host ("sending " + $vstsCommandString) - Write-Host "##$vstsCommandString" - displayName: Set packageName variable - - - task: DownloadBuildArtifacts@0 - inputs: - artifactName: 'results' - itemPattern: '**/*$(PkgFilter).zip' - downloadPath: '$(System.ArtifactsDirectory)\Symbols' - - - template: cloneToOfficialPath.yml - - - pwsh: | - $zipPathString = '$(System.ArtifactsDirectory)\Symbols\results\*$(PkgFilter).zip' - Write-Verbose -Verbose "Zip Path: $zipPathString" - $zipPath = Get-Item $zipPathString - if(@($zipPath).Count -eq 0) { - throw "No files found at '$zipPathString'" - } - elseif(@($zipPath).Count -ne 1) { - $names = $zipPath.Name -join "', '" - throw "multiple files '${names}' found with '${zipPathString}'" - } - - $expandedFolder = $zipPath.BaseName - Write-Host "sending.. vso[task.setvariable variable=SymbolsFolder]$expandedFolder" - Write-Host "##vso[task.setvariable variable=SymbolsFolder]$expandedFolder" - - Expand-Archive -Path $zipPath -Destination "$(System.ArtifactsDirectory)\$expandedFolder" -Force - displayName: Expand symbols zip - - - pwsh: | - $fullSymbolsFolder = "$(System.ArtifactsDirectory)\$($env:SYMBOLSFOLDER)" - - $filesToSignDirectory = "$(System.ArtifactsDirectory)\toBeSigned" - $null = New-Item -ItemType Directory -Path $filesToSignDirectory -Force - - $signedFilesDirectory = "$(System.ArtifactsDirectory)\signed" - $null = New-Item -ItemType Directory -Path $signedFilesDirectory -Force - - $itemsToCopyWithRecurse = @( - "$($fullSymbolsFolder)\*.ps1" - "$($fullSymbolsFolder)\Microsoft.PowerShell*.dll" - ) - - $itemsToCopy = @{ - "$($fullSymbolsFolder)\*.ps1" = "" - "$($fullSymbolsFolder)\Microsoft.Management.Infrastructure.CimCmdlets.dll" = "" - "$($fullSymbolsFolder)\Microsoft.WSMan.*.dll" = "" - "$($fullSymbolsFolder)\Modules\CimCmdlets\CimCmdlets.psd1" = "Modules\CimCmdlets" - "$($fullSymbolsFolder)\Modules\Microsoft.PowerShell.Diagnostics\Diagnostics.format.ps1xml" = "Modules\Microsoft.PowerShell.Diagnostics" - "$($fullSymbolsFolder)\Modules\Microsoft.PowerShell.Diagnostics\Event.format.ps1xml" = "Modules\Microsoft.PowerShell.Diagnostics" - "$($fullSymbolsFolder)\Modules\Microsoft.PowerShell.Diagnostics\GetEvent.types.ps1xml" = "Modules\Microsoft.PowerShell.Diagnostics" - "$($fullSymbolsFolder)\Modules\Microsoft.PowerShell.Diagnostics\Microsoft.PowerShell.Diagnostics.psd1" = "Modules\Microsoft.PowerShell.Diagnostics" - "$($fullSymbolsFolder)\Modules\Microsoft.PowerShell.Host\Microsoft.PowerShell.Host.psd1" = "Modules\Microsoft.PowerShell.Host" - "$($fullSymbolsFolder)\Modules\Microsoft.PowerShell.Management\Microsoft.PowerShell.Management.psd1" = "Modules\Microsoft.PowerShell.Management" - "$($fullSymbolsFolder)\Modules\Microsoft.PowerShell.Security\Microsoft.PowerShell.Security.psd1" = "Modules\Microsoft.PowerShell.Security" - "$($fullSymbolsFolder)\Modules\Microsoft.PowerShell.Security\Security.types.ps1xml" = "Modules\Microsoft.PowerShell.Security" - "$($fullSymbolsFolder)\Modules\Microsoft.PowerShell.Utility\Microsoft.PowerShell.Utility.psd1" = "Modules\Microsoft.PowerShell.Utility" - "$($fullSymbolsFolder)\Modules\Microsoft.WSMan.Management\Microsoft.WSMan.Management.psd1" = "Modules\Microsoft.WSMan.Management" - "$($fullSymbolsFolder)\Modules\Microsoft.WSMan.Management\WSMan.format.ps1xml" = "Modules\Microsoft.WSMan.Management" - "$($fullSymbolsFolder)\Modules\PSDiagnostics\PSDiagnostics.ps?1" = "Modules\PSDiagnostics" - "$($fullSymbolsFolder)\pwsh.dll" = "" - "$($fullSymbolsFolder)\System.Management.Automation.dll" = "" - "$($fullSymbolsFolder)\pwsh.exe" = "" - } - - $itemsToExclude = @( - # This package is retrieved from https://www.github.com/powershell/MarkdownRender - "$($fullSymbolsFolder)\Microsoft.PowerShell.MarkdownRender.dll" - ) - - Write-Verbose -verbose "recusively copying $($itemsToCopyWithRecurse | out-string) to $filesToSignDirectory" - Copy-Item -Path $itemsToCopyWithRecurse -Destination $filesToSignDirectory -Recurse -verbose -exclude $itemsToExclude - - foreach($pattern in $itemsToCopy.Keys) { - $destinationFolder = Join-Path $filesToSignDirectory -ChildPath $itemsToCopy.$pattern - $null = New-Item -ItemType Directory -Path $destinationFolder -Force - Write-Verbose -verbose "copying $pattern to $destinationFolder" - Copy-Item -Path $pattern -Destination $destinationFolder -Recurse -verbose - } - displayName: 'Prepare files to be signed' - - - template: EsrpSign.yml@ComplianceRepo - parameters: - buildOutputPath: $(System.ArtifactsDirectory)\toBeSigned - signOutputPath: $(System.ArtifactsDirectory)\signed - certificateId: "$(AUTHENTICODE_CERT)" - pattern: | - **\*.dll - **\*.psd1 - **\*.psm1 - **\*.ps1xml - **\*.ps1 - **\*.exe - useMinimatch: true - shouldSign: $(SHOULD_SIGN) - displayName: Sign our binaries - - - pwsh: | - Import-Module $(PowerShellRoot)/build.psm1 -Force - Import-Module $(PowerShellRoot)/tools/packaging -Force - $signedFilesPath = '$(System.ArtifactsDirectory)\signed\' - $BuildPath = '$(System.ArtifactsDirectory)\$(SymbolsFolder)' - - Update-PSSignedBuildFolder -BuildPath $BuildPath -SignedFilesPath $SignedFilesPath - $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 - - Write-Verbose -verbose "to be signed:`r`n $($missingSignatures | Out-String)" - - $filesToSignDirectory = "$(System.ArtifactsDirectory)\thirdPartyToBeSigned" - $null = New-Item -ItemType Directory -Path $filesToSignDirectory -Force - - $signedFilesDirectory = "$(System.ArtifactsDirectory)\thirdPartySigned" - $null = New-Item -ItemType Directory -Path $signedFilesDirectory -Force - - $missingSignatures | ForEach-Object { - $pathWithoutLeaf = Split-Path $_ - $relativePath = $pathWithoutLeaf.replace($BuildPath,'') - $targetDirectory = Join-Path -Path $filesToSignDirectory -ChildPath $relativePath - if(!(Test-Path $targetDirectory)) - { - $null = New-Item -ItemType Directory -Path $targetDirectory -Force - } - Copy-Item -Path $_ -Destination $targetDirectory - } - - displayName: Create ThirdParty Signing Folder - condition: and(succeeded(), eq(variables['SHOULD_SIGN'], 'true')) - - - template: EsrpSign.yml@ComplianceRepo - parameters: - buildOutputPath: $(System.ArtifactsDirectory)\thirdPartyToBeSigned - signOutputPath: $(System.ArtifactsDirectory)\thirdPartySigned - certificateId: "CP-231522" - pattern: | - **\*.dll - useMinimatch: true - shouldSign: $(SHOULD_SIGN) - displayName: Sign ThirdParty binaries - - - pwsh: | - Get-ChildItem '$(System.ArtifactsDirectory)\thirdPartySigned\*' - displayName: Capture ThirdParty Signed files - condition: and(succeeded(), eq(variables['SHOULD_SIGN'], 'true')) - - - pwsh: | - Import-Module $(PowerShellRoot)/build.psm1 -Force - Import-Module $(PowerShellRoot)/tools/packaging -Force - $signedFilesPath = '$(System.ArtifactsDirectory)\thirdPartySigned' - $BuildPath = '$(System.ArtifactsDirectory)\$(SymbolsFolder)' - - Update-PSSignedBuildFolder -BuildPath $BuildPath -SignedFilesPath $SignedFilesPath - if ($env:BuildConfiguration -eq 'minSize') { - ## Remove XML files when making a min-size package. - Remove-Item "$BuildPath/*.xml" -Force - } - displayName: Merge ThirdParty signed files with Build - condition: and(succeeded(), eq(variables['SHOULD_SIGN'], 'true')) - - - template: Sbom.yml@ComplianceRepo - parameters: - BuildDropPath: '$(System.ArtifactsDirectory)\$(SymbolsFolder)' - Build_Repository_Uri: $(Github_Build_Repository_Uri) - PackageName: PowerShell Windows ${{ parameters.Architecture }} ${{ parameters.BuildConfiguration }} - PackageVersion: $(Version) - sourceScanPath: '$(PowerShellRoot)\tools' - - - pwsh: | - Import-Module $(PowerShellRoot)/build.psm1 -Force - Import-Module $(PowerShellRoot)/tools/packaging -Force - - $destFolder = '$(System.ArtifactsDirectory)\signedZip' - $BuildPath = '$(System.ArtifactsDirectory)\$(SymbolsFolder)' - - New-Item -ItemType Directory -Path $destFolder -Force - - $BuildPackagePath = New-PSBuildZip -BuildPath $BuildPath -DestinationFolder $destFolder - - Write-Verbose -Verbose "New-PSSignedBuildZip returned `$BuildPackagePath as: $BuildPackagePath" - Write-Host "##vso[artifact.upload containerfolder=results;artifactname=results]$BuildPackagePath" - - $vstsCommandString = "vso[task.setvariable variable=BuildPackagePath]$BuildPackagePath" - Write-Host ("sending " + $vstsCommandString) - Write-Host "##$vstsCommandString" - displayName: Compress signed files - retryCountOnTaskFailure: 2 - - - - pwsh: | - $runtime = switch ($env:Architecture) - { - "x64" { "win7-x64" } - "x86" { "win7-x86" } - "arm64" { "win-arm64" } - "fxdependent" { "fxdependent" } - "fxdependentWinDesktop" { "fxdependent-win-desktop" } - } - - $signedPkg = "$(BuildPackagePath)" - Write-Verbose -Verbose -Message "signedPkg = $signedPkg" - - $params = @{} - if ($env:BuildConfiguration -eq 'minSize') { - $params['ForMinimalSize'] = $true - } - - $(PowerShellRoot)/tools/releaseBuild/Images/microsoft_powershell_windowsservercore/PowerShellPackage.ps1 -BuildZip $signedPkg -location '$(PowerShellRoot)' -destination '$(System.ArtifactsDirectory)\pkgSigned' -Runtime $runtime -ReleaseTag '$(ReleaseTagVar)' @params - displayName: 'Build Windows Universal - $(Architecture) Package' - env: - __DOTNET_RUNTIME_FEED_KEY: $(RUNTIME_SOURCEFEED_KEY) - - - pwsh: | - Get-ChildItem '$(System.ArtifactsDirectory)\pkgSigned' | ForEach-Object { - $packagePath = $_.FullName - Write-Host "Uploading $packagePath" - Write-Host "##vso[artifact.upload containerfolder=signed;artifactname=signed]$packagePath" - } - displayName: Upload unsigned packages - retryCountOnTaskFailure: 2 - - - ${{ if and(ne(variables['BuildConfiguration'],'minSize'), in(variables['Architecture'], 'x64', 'x86', 'arm64')) }}: - - template: EsrpSign.yml@ComplianceRepo - parameters: - buildOutputPath: $(System.ArtifactsDirectory)\pkgSigned - signOutputPath: $(Build.StagingDirectory)\signedPackages - certificateId: "$(AUTHENTICODE_CERT)" - pattern: | - **\*.msi - useMinimatch: true - shouldSign: $(SHOULD_SIGN) - displayName: Sign MSI - alwaysCopy: true - - - pwsh: | - Get-ChildItem '$(System.ArtifactsDirectory)\signedPackages' | ForEach-Object { - $packagePath = $_.FullName - Write-Host "Uploading $packagePath" - Write-Host "##vso[artifact.upload containerfolder=finalResults;artifactname=finalResults]$packagePath" - } - displayName: Upload signed MSI to finalResults - retryCountOnTaskFailure: 2 - - - task: AzureFileCopy@4 - displayName: 'upload signed msi to Azure - ${{ parameters.architecture }}' - inputs: - SourcePath: '$(Build.StagingDirectory)\signedPackages\PowerShell-$(version)-win-${{ parameters.architecture }}.msi' - azureSubscription: '$(AzureFileCopySubscription)' - Destination: AzureBlob - storage: '$(StorageAccount)' - ContainerName: '$(AzureVersion)' - resourceGroup: '$(StorageResourceGroup)' - retryCountOnTaskFailure: 2 - - - pwsh: | - cd $(PowerShellRoot) - Import-Module $(PowerShellRoot)/build.psm1 -Force - Import-Module $(PowerShellRoot)/tools/packaging -Force - - $msiPath = '$(Build.StagingDirectory)\signedPackages\PowerShell-$(version)-win-${{ parameters.architecture }}.msi' - - New-ExePackage -ProductVersion '$(version)' -MsiLocationPath $msiPath -ProductTargetArchitecture ${{ parameters.architecture }} - $exePath = Get-ChildItem '.\PowerShell-*.exe' | Select-Object -First 1 -ExpandProperty fullname - $enginePath = Join-Path -Path '$(System.ArtifactsDirectory)\unsignedEngine' -ChildPath engine.exe - # Expand Burn Engine so we can sign it. - Expand-ExePackageEngine -ExePath $exePath -EnginePath $enginePath - displayName: Create exe wrapper - - - template: EsrpSign.yml@ComplianceRepo - parameters: - buildOutputPath: $(System.ArtifactsDirectory)\unsignedEngine - signOutputPath: $(System.ArtifactsDirectory)\signedEngine - certificateId: "$(AUTHENTICODE_CERT)" - pattern: | - **\*.exe - useMinimatch: true - shouldSign: $(SHOULD_SIGN) - displayName: Sign Burn Engine - alwaysCopy: true - - - pwsh: | - cd '$(PowerShellRoot)' - Import-Module '$(PowerShellRoot)/build.psm1' -Force - Import-Module '$(PowerShellRoot)/tools/packaging' -Force - - $exePath = Get-ChildItem '.\PowerShell-*.exe' | Select-Object -First 1 -ExpandProperty fullname - $enginePath = Join-Path -Path '$(System.ArtifactsDirectory)\signedEngine' -ChildPath engine.exe - $enginePath | Get-AuthenticodeSignature | out-string | Write-Verbose -verbose - Compress-ExePackageEngine -ExePath $exePath -EnginePath $enginePath - displayName: Re-attach the signed Burn engine in exe wrapper - - - pwsh: | - cd '$(PowerShellRoot)' - Get-ChildItem '.\PowerShell-*.exe' | ForEach-Object { - $packagePath = $_.FullName - Write-Host "Uploading $packagePath" - Write-Host "##vso[artifact.upload containerfolder=signed;artifactname=signed]$packagePath" - } - displayName: Upload unsigned exe - retryCountOnTaskFailure: 2 - - - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 - displayName: 'Component Detection' - inputs: - sourceScanPath: '$(PowerShellRoot)\tools' - snapshotForceEnabled: true - - - pwsh: | - if ((Test-Path "\PowerShell")) { - Remove-Item -Path "\PowerShell" -Force -Recurse -Verbose - } - else { - Write-Verbose -Verbose -Message "No cleanup required." - } - displayName: Clean up local Clone - condition: always() - - - template: /tools/releaseBuild/azureDevOps/templates/step/finalize.yml diff --git a/tools/releaseBuild/azureDevOps/vpackRelease.yml b/tools/releaseBuild/azureDevOps/vpackRelease.yml deleted file mode 100644 index 14368ffb8f8..00000000000 --- a/tools/releaseBuild/azureDevOps/vpackRelease.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: vpack-$(Build.BuildId) -trigger: - branches: - include: - - master - - release* -pr: - branches: - include: - - master - - release* - -variables: - - name: DOTNET_CLI_TELEMETRY_OPTOUT - value: 1 - - name: POWERSHELL_TELEMETRY_OPTOUT - value: 1 - - name: nugetMultiFeedWarnLevel - value: none - - - group: Azure Blob variable group - # adds the pat to publish the vPack - # instructions to create are in the description of the library - - group: vPack - -stages: -- stage: prep - displayName: Create buildInfo and name the Pipeline - jobs: - - job: rename - displayName: Name the build - condition: succeeded() - - pool: - name: PowerShell1ES - demands: - - ImageOverride -equals PSMMS2019-Secure - - steps: - - checkout: self - clean: true - - - template: ./templates/SetVersionVariables.yml - parameters: - ReleaseTagVar: $(ReleaseTagVar) - CreateJson: yes - UseJson: no - - - powershell: | - if($env:RELEASETAGVAR -match '-') { - throw "Don't release a preview build without coordinating with Windows Engineering Build Tools Team" - } - displayName: Stop any preview release - - - powershell: Write-Host "##vso[build.updatebuildnumber]$env:BUILD_SOURCEBRANCHNAME-$env:BUILD_SOURCEVERSION-$((get-date).ToString("yyyyMMddhhss"))" - displayName: Set Build Name for Non-PR - condition: ne(variables['Build.Reason'], 'PullRequest') - -- stage: release - displayName: Release - jobs: - - template: ./templates/vpackReleaseJob.yml - parameters: - architecture: x64 - - - template: ./templates/vpackReleaseJob.yml - parameters: - architecture: x86 - - - template: ./templates/vpackReleaseJob.yml - parameters: - architecture: arm64 diff --git a/tools/releaseBuild/build.json b/tools/releaseBuild/build.json deleted file mode 100644 index fe2f9d96f17..00000000000 --- a/tools/releaseBuild/build.json +++ /dev/null @@ -1,336 +0,0 @@ -{ - "Windows": [ - { - "Name": "win7-x64", - "RepoDestinationPath": "C:\\PowerShell", - "BuildCommand": "C:\\PowerShellPackage.ps1 -location _RepoDestinationPath_ -destination _DockerVolume_ -Runtime win7-x64 -ReleaseTag _ReleaseTag_", - "BuildDockerOptions": [ - "-m", - "3968m" - ], - "DockerFile": ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\DockerFile", - "AdditionalContextFiles" :[ - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\PowerShellPackage.ps1", - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\wix.psm1", - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\dockerInstall.psm1" - ], - "DockerImageName": "ps-winsrvcore", - "BinaryBucket": "release", - "EnableFeature": [ "ArtifactAsFolder" ] - }, - { - "Name": "win7-x86", - "RepoDestinationPath": "C:\\PowerShell", - "BuildCommand": "C:\\PowerShellPackage.ps1 -location _RepoDestinationPath_ -destination _DockerVolume_ -Runtime win7-x86 -ReleaseTag _ReleaseTag_", - "BuildDockerOptions": [ - "-m", - "3968m" - ], - "DockerFile": ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\Dockerfile", - "AdditionalContextFiles" :[ - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\PowerShellPackage.ps1", - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\wix.psm1", - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\dockerInstall.psm1" - ], - "DockerImageName": "ps-winsrvcore", - "BinaryBucket": "release", - "EnableFeature": [ "ArtifactAsFolder" ] - }, - { - "Name": "win-x64-component-registration", - "RepoDestinationPath": "C:\\PowerShell", - "BuildCommand": "C:\\PowerShellPackage.ps1 -location _RepoDestinationPath_ -destination _DockerVolume_ -Runtime win7-x64 -ReleaseTag _ReleaseTag_ -ComponentRegistration", - "BuildDockerOptions": [ - "-m", - "3968m" - ], - "DockerFile": ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\Dockerfile", - "AdditionalContextFiles" :[ - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\PowerShellPackage.ps1", - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\wix.psm1", - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\dockerInstall.psm1" - ], - "DockerImageName": "ps-winsrvcore", - "BinaryBucket": "results", - "ArtifactsExpected": 1, - "VariableForExtractedBinariesPath": "componentregistration", - "EnableFeature": [ "ArtifactAsFolder" ] - }, - { - "Name": "win-x64-symbols", - "RepoDestinationPath": "C:\\PowerShell", - "BuildCommand": "C:\\PowerShellPackage.ps1 -location _RepoDestinationPath_ -destination _DockerVolume_ -Runtime win7-x64 -ReleaseTag _ReleaseTag_ -Symbols", - "BuildDockerOptions": [ - "-m", - "3968m" - ], - "DockerFile": ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\Dockerfile", - "AdditionalContextFiles" :[ - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\PowerShellPackage.ps1", - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\wix.psm1", - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\dockerInstall.psm1" - ], - "DockerImageName": "ps-winsrvcore", - "BinaryBucket": "results", - "ArtifactsExpected": 1, - "VariableForExtractedBinariesPath": "Symbols_x64", - "EnableFeature": [ "ArtifactAsFolder" ] - }, - { - "Name": "win-x86-symbols", - "RepoDestinationPath": "C:\\PowerShell", - "BuildCommand": "C:\\PowerShellPackage.ps1 -location _RepoDestinationPath_ -destination _DockerVolume_ -Runtime win7-x86 -ReleaseTag _ReleaseTag_ -Symbols", - "BuildDockerOptions": [ - "-m", - "3968m" - ], - "DockerFile": ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\Dockerfile", - "AdditionalContextFiles" :[ - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\PowerShellPackage.ps1", - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\wix.psm1", - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\dockerInstall.psm1" - ], - "DockerImageName": "ps-winsrvcore", - "BinaryBucket": "results", - "ArtifactsExpected": 1, - "VariableForExtractedBinariesPath": "Symbols_x86", - "EnableFeature": [ "ArtifactAsFolder" ] - }, - { - "Name": "win-arm-symbols", - "RepoDestinationPath": "C:\\PowerShell", - "BuildCommand": "C:\\PowerShellPackage.ps1 -location _RepoDestinationPath_ -destination _DockerVolume_ -Runtime win-arm -ReleaseTag _ReleaseTag_ -Symbols", - "BuildDockerOptions": [ - "-m", - "3968m" - ], - "DockerFile": ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\Dockerfile", - "AdditionalContextFiles" :[ - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\PowerShellPackage.ps1", - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\wix.psm1", - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\dockerInstall.psm1" - ], - "DockerImageName": "ps-winsrvcore", - "BinaryBucket": "results", - "ArtifactsExpected": 1, - "VariableForExtractedBinariesPath": "Symbols_arm", - "EnableFeature": [ "ArtifactAsFolder" ] - }, - { - "Name": "win-arm64-symbols", - "RepoDestinationPath": "C:\\PowerShell", - "BuildCommand": "C:\\PowerShellPackage.ps1 -location _RepoDestinationPath_ -destination _DockerVolume_ -Runtime win-arm64 -ReleaseTag _ReleaseTag_ -Symbols", - "BuildDockerOptions": [ - "-m", - "3968m" - ], - "DockerFile": ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\Dockerfile", - "AdditionalContextFiles" :[ - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\PowerShellPackage.ps1", - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\wix.psm1", - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\dockerInstall.psm1" - ], - "DockerImageName": "ps-winsrvcore", - "BinaryBucket": "results", - "ArtifactsExpected": 1, - "VariableForExtractedBinariesPath": "Symbols_arm64", - "EnableFeature": [ "ArtifactAsFolder" ] - }, - { - "Name": "win-x64-package", - "RepoDestinationPath": "C:\\PowerShell", - "BuildCommand": "C:\\PowerShellPackage.ps1 -BuildZip _RepoDestinationPath_\\_BuildPackageName_ -location _RepoDestinationPath_ -destination _DockerVolume_ -Runtime win7-x64 -ReleaseTag _ReleaseTag_", - "BuildDockerOptions": [ - "-m", - "3968m" - ], - "DockerFile": ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\Dockerfile", - "AdditionalContextFiles" :[ - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\PowerShellPackage.ps1", - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\wix.psm1", - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\dockerInstall.psm1" - ], - "DockerImageName": "ps-winsrvcore", - "BinaryBucket": "signed", - "ArtifactsExpected": 4, - "EnableFeature": [ "ArtifactAsFolder" ] - }, - { - "Name": "win-x86-package", - "RepoDestinationPath": "C:\\PowerShell", - "BuildCommand": "C:\\PowerShellPackage.ps1 -BuildZip _RepoDestinationPath_\\_BuildPackageName_ -location _RepoDestinationPath_ -destination _DockerVolume_ -Runtime win7-x86 -ReleaseTag _ReleaseTag_", - "BuildDockerOptions": [ - "-m", - "3968m" - ], - "DockerFile": ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\Dockerfile", - "AdditionalContextFiles" :[ - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\PowerShellPackage.ps1", - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\wix.psm1", - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\dockerInstall.psm1" - ], - "DockerImageName": "ps-winsrvcore", - "BinaryBucket": "signed", - "ArtifactsExpected": 4, - "EnableFeature": [ "ArtifactAsFolder" ] - }, - { - "Name": "win-arm-package", - "RepoDestinationPath": "C:\\PowerShell", - "BuildCommand": "C:\\PowerShellPackage.ps1 -BuildZip _RepoDestinationPath_\\_BuildPackageName_ -location _RepoDestinationPath_ -destination _DockerVolume_ -Runtime win-arm -ReleaseTag _ReleaseTag_", - "BuildDockerOptions": [ - "-m", - "3968m" - ], - "DockerFile": ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\Dockerfile", - "AdditionalContextFiles" :[ - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\PowerShellPackage.ps1", - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\wix.psm1", - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\dockerInstall.psm1" - ], - "DockerImageName": "ps-winsrvcore", - "BinaryBucket": "signed", - "ArtifactsExpected": 2, - "EnableFeature": [ "ArtifactAsFolder" ] - }, - { - "Name": "win-arm64-package", - "RepoDestinationPath": "C:\\PowerShell", - "BuildCommand": "C:\\PowerShellPackage.ps1 -BuildZip _RepoDestinationPath_\\_BuildPackageName_ -location _RepoDestinationPath_ -destination _DockerVolume_ -Runtime win-arm64 -ReleaseTag _ReleaseTag_", - "BuildDockerOptions": [ - "-m", - "3968m" - ], - "DockerFile": ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\Dockerfile", - "AdditionalContextFiles" :[ - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\PowerShellPackage.ps1", - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\wix.psm1", - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\dockerInstall.psm1" - ], - "DockerImageName": "ps-winsrvcore", - "BinaryBucket": "signed", - "ArtifactsExpected": 2, - "EnableFeature": [ "ArtifactAsFolder" ] - }, - { - "Name": "win-fxdependent-symbols", - "RepoDestinationPath": "C:\\PowerShell", - "BuildCommand": "C:\\PowerShellPackage.ps1 -location _RepoDestinationPath_ -destination _DockerVolume_ -Runtime fxdependent -ReleaseTag _ReleaseTag_ -Symbols", - "BuildDockerOptions": [ - "-m", - "3968m" - ], - "DockerFile": ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\Dockerfile", - "AdditionalContextFiles" :[ - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\PowerShellPackage.ps1", - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\wix.psm1", - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\dockerInstall.psm1" - ], - "DockerImageName": "ps-winsrvcore", - "BinaryBucket": "results", - "ArtifactsExpected": 1, - "VariableForExtractedBinariesPath": "Symbols_fxdependent", - "EnableFeature": [ "ArtifactAsFolder" ] - }, - { - "Name": "win-fxdependent-package", - "RepoDestinationPath": "C:\\PowerShell", - "BuildCommand": "C:\\PowerShellPackage.ps1 -BuildZip _RepoDestinationPath_\\_BuildPackageName_ -location _RepoDestinationPath_ -destination _DockerVolume_ -Runtime fxdependent -ReleaseTag _ReleaseTag_", - "BuildDockerOptions": [ - "-m", - "3968m" - ], - "DockerFile": ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\Dockerfile", - "AdditionalContextFiles" :[ - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\PowerShellPackage.ps1", - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\wix.psm1", - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\dockerInstall.psm1" - ], - "DockerImageName": "ps-winsrvcore", - "BinaryBucket": "signed", - "ArtifactsExpected": 1, - "EnableFeature": [ "ArtifactAsFolder" ] - }, - { - "Name": "win-fxdependentWinDesktop-symbols", - "RepoDestinationPath": "C:\\PowerShell", - "BuildCommand": "C:\\PowerShellPackage.ps1 -location _RepoDestinationPath_ -destination _DockerVolume_ -Runtime fxdependent-win-desktop -ReleaseTag _ReleaseTag_ -Symbols", - "BuildDockerOptions": [ - "-m", - "3968m" - ], - "DockerFile": ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\Dockerfile", - "AdditionalContextFiles" :[ - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\PowerShellPackage.ps1", - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\wix.psm1", - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\dockerInstall.psm1" - ], - "DockerImageName": "ps-winsrvcore", - "BinaryBucket": "results", - "ArtifactsExpected": 1, - "VariableForExtractedBinariesPath": "Symbols_fxdependentWinDesktop", - "EnableFeature": [ "ArtifactAsFolder" ] - }, - { - "Name": "win-fxdependentWinDesktop-package", - "RepoDestinationPath": "C:\\PowerShell", - "BuildCommand": "C:\\PowerShellPackage.ps1 -BuildZip _RepoDestinationPath_\\_BuildPackageName_ -location _RepoDestinationPath_ -destination _DockerVolume_ -Runtime fxdependent-win-desktop -ReleaseTag _ReleaseTag_", - "BuildDockerOptions": [ - "-m", - "3968m" - ], - "DockerFile": ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\Dockerfile", - "AdditionalContextFiles" :[ - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\PowerShellPackage.ps1", - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\wix.psm1", - ".\\tools\\releaseBuild\\Images\\microsoft_powershell_windowsservercore\\dockerInstall.psm1" - ], - "DockerImageName": "ps-winsrvcore", - "BinaryBucket": "signed", - "ArtifactsExpected": 1, - "EnableFeature": [ "ArtifactAsFolder" ] - } - ], - "Linux": [ - { - "Name": "deb", - "RepoDestinationPath": "/PowerShell", - "BuildCommand": "/PowerShellPackage.ps1 -location _RepoDestinationPath_ -destination _DockerVolume_ -ReleaseTag _ReleaseTag_ -TarX64 -TarArm -TarArm64 -TarMinSize", - "DockerFile": "./tools/releaseBuild/Images/microsoft_powershell_ubuntu18.04/Dockerfile", - "AdditionalContextFiles" :[ "./tools/releaseBuild/Images/GenericLinuxFiles/PowerShellPackage.ps1"], - "DockerImageName": "ps-ubunutu-18-04", - "BinaryBucket": "release", - "EnableFeature": [ "ArtifactAsFolder" ] - }, - { - "Name": "rpm", - "RepoDestinationPath": "/PowerShell", - "BuildCommand": "/PowerShellPackage.ps1 -location _RepoDestinationPath_ -destination _DockerVolume_ -ReleaseTag _ReleaseTag_", - "AdditionalContextFiles" :[ "./tools/releaseBuild/Images/GenericLinuxFiles/PowerShellPackage.ps1"], - "DockerFile": "./tools/releaseBuild/Images/microsoft_powershell_centos7/Dockerfile", - "DockerImageName": "ps-centos-7", - "BinaryBucket": "release", - "EnableFeature": [ "ArtifactAsFolder" ] - }, - { - "Name": "alpine", - "RepoDestinationPath": "/PowerShell", - "BuildCommand": "/PowerShellPackage.ps1 -location _RepoDestinationPath_ -destination _DockerVolume_ -ReleaseTag _ReleaseTag_ -Alpine", - "AdditionalContextFiles" :[ "./tools/releaseBuild/Images/GenericLinuxFiles/PowerShellPackage.ps1"], - "DockerFile": "./tools/releaseBuild/Images/microsoft_powershell_alpine3/Dockerfile", - "DockerImageName": "ps-alpine-3", - "BinaryBucket": "release", - "EnableFeature": [ "ArtifactAsFolder" ] - }, - { - "Name": "fxdependent", - "RepoDestinationPath": "/PowerShell", - "BuildCommand": "/PowerShellPackage.ps1 -location _RepoDestinationPath_ -destination _DockerVolume_ -ReleaseTag _ReleaseTag_ -FxDependent", - "AdditionalContextFiles" :[ "./tools/releaseBuild/Images/GenericLinuxFiles/PowerShellPackage.ps1"], - "DockerFile": "./tools/releaseBuild/Images/microsoft_powershell_centos7/Dockerfile", - "DockerImageName": "ps-centos-7", - "BinaryBucket": "release", - "EnableFeature": [ "ArtifactAsFolder" ] - } - ] -} diff --git a/tools/releaseBuild/createComplianceFolder.ps1 b/tools/releaseBuild/createComplianceFolder.ps1 deleted file mode 100644 index c462a09ebdb..00000000000 --- a/tools/releaseBuild/createComplianceFolder.ps1 +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -param( - [Parameter(HelpMessage="Artifact folder to find compliance files in.")] - [string[]] - $ArtifactFolder, - [Parameter(HelpMessage="VSTS Variable to set path to complinance Files.")] - [string] - $VSTSVariableName -) - -$compliancePath = $null -foreach($folder in $ArtifactFolder) -{ - # Find Symbols zip which contains compliance files - Write-Host "ArtifactFolder: $folder" - $filename = Join-Path -Path $folder -ChildPath 'symbols.zip' - - $parentName = Split-Path -Path $folder -Leaf - - # Use simplified names because some of the compliance tools didn't like the full names - # decided not to use hashes because the names need to be consistent otherwise the tool also has issues - # which is another problem with the full name, it includes version. - if ($parentName -match 'x64' -or $parentName -match 'amd64') - { - $name = 'x64' - } - elseif ($parentName -match 'x86') { - $name = 'x86' - } - elseif ($parentName -match 'fxdependent') { - $name = 'fxd' - } - else - { - throw "$parentName could not be classified as x86 or x64" - } - - # Throw is compliance zip does not exist - if (!(Test-Path $filename)) - { - throw "symbols.zip for $VSTSVariableName does not exist" - } - - # make sure we have a single parent for everything - if (!$compliancePath) - { - $parent = Split-Path -Path $folder - $compliancePath = Join-Path -Path $parent -ChildPath 'compliance' - } - - # Extract complance files to individual folder to avoid overwriting files. - $unzipPath = Join-Path -Path $compliancePath -ChildPath $name - Write-Host "Symbols-zip: $filename ; unzipPath: $unzipPath" - Expand-Archive -Path $fileName -DestinationPath $unzipPath -} - -# set VSTS variable with path to compliance files -Write-Host "##vso[task.setvariable variable=$VSTSVariableName]$unzipPath" diff --git a/tools/releaseBuild/generatePackgeSigning.ps1 b/tools/releaseBuild/generatePackgeSigning.ps1 deleted file mode 100644 index ff848892097..00000000000 --- a/tools/releaseBuild/generatePackgeSigning.ps1 +++ /dev/null @@ -1,112 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -param( - [Parameter(Mandatory)] - [string] $Path, - [string[]] $AuthenticodeDualFiles, - [string[]] $AuthenticodeFiles, - [string[]] $NuPkgFiles, - [string[]] $MacDeveloperFiles, - [string[]] $LinuxFiles, - [string[]] $ThirdPartyFiles, - [string[]] $MsixFiles, - [ValidateSet('release','preview')] - [string] $MsixCertType = 'preview' -) - -if ((!$AuthenticodeDualFiles -or $AuthenticodeDualFiles.Count -eq 0) -and - (!$AuthenticodeFiles -or $AuthenticodeFiles.Count -eq 0) -and - (!$NuPkgFiles -or $NuPkgFiles.Count -eq 0) -and - (!$MacDeveloperFiles -or $MacDeveloperFiles.Count -eq 0) -and - (!$LinuxFiles -or $LinuxFiles.Count -eq 0) -and - (!$MsixFiles -or $MsixFiles.Count -eq 0) -and - (!$ThirdPartyFiles -or $ThirdPartyFiles.Count -eq 0)) -{ - throw "At least one file must be specified" -} - -function New-Attribute -{ - param( - [Parameter(Mandatory)] - [string]$Name, - [Parameter(Mandatory)] - [object]$Value, - [Parameter(Mandatory)] - [System.Xml.XmlElement]$Element - ) - - $attribute = $signingXml.CreateAttribute($Name) - $attribute.Value = $value - $null = $fileElement.Attributes.Append($attribute) -} - -function New-FileElement -{ - param( - [Parameter(Mandatory)] - [string]$File, - [Parameter(Mandatory)] - [string]$SignType, - [Parameter(Mandatory)] - [System.Xml.XmlDocument]$XmlDoc, - [Parameter(Mandatory)] - [System.Xml.XmlElement]$Job - ) - - if(Test-Path -Path $file) - { - $name = Split-Path -Leaf -Path $File - $fileElement = $XmlDoc.CreateElement("file") - New-Attribute -Name 'src' -value $file -Element $fileElement - New-Attribute -Name 'signType' -value $SignType -Element $fileElement - New-Attribute -Name 'dest' -value "__OUTPATHROOT__\$name" -Element $fileElement - $null = $job.AppendChild($fileElement) - } - else - { - Write-Warning -Message "Skipping $SignType; $File because it does not exist" - } -} - -[xml]$signingXml = Get-Content (Join-Path -Path $PSScriptRoot -ChildPath 'packagesigning.xml') -$job = $signingXml.SignConfigXML.job - -foreach($file in $AuthenticodeDualFiles) -{ - New-FileElement -File $file -SignType 'AuthenticodeDual' -XmlDoc $signingXml -Job $job -} - -foreach($file in $AuthenticodeFiles) -{ - New-FileElement -File $file -SignType 'AuthenticodeFormer' -XmlDoc $signingXml -Job $job -} - -foreach($file in $NuPkgFiles) -{ - New-FileElement -File $file -SignType 'NuGet' -XmlDoc $signingXml -Job $job -} - -foreach ($file in $MacDeveloperFiles) { - New-FileElement -File $file -SignType 'MacDeveloper' -XmlDoc $signingXml -Job $job -} - -foreach ($file in $LinuxFiles) { - New-FileElement -File $file -SignType 'LinuxPack' -XmlDoc $signingXml -Job $job -} - -foreach ($file in $ThirdPartyFiles) { - New-FileElement -File $file -SignType 'ThirdParty' -XmlDoc $signingXml -Job $job -} - -foreach ($file in $MsixFiles) { - # 'CP-459155' is supposed to work for the store - # AuthenticodeFormer works for sideloading and via a workaround, through the store - # ---------------------------------------------- - # update releasePublisher in packaging.psm1 when this is changed - New-FileElement -File $file -SignType 'AuthenticodeFormer' -XmlDoc $signingXml -Job $job -} - -$signingXml.Save($path) -$updateScriptPath = Join-Path -Path $PSScriptRoot -ChildPath 'updateSigning.ps1' -& $updateScriptPath -SigningXmlPath $path diff --git a/tools/releaseBuild/macOS/PowerShellPackageVsts.ps1 b/tools/releaseBuild/macOS/PowerShellPackageVsts.ps1 deleted file mode 100644 index acedbdd3388..00000000000 --- a/tools/releaseBuild/macOS/PowerShellPackageVsts.ps1 +++ /dev/null @@ -1,143 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -# PowerShell Script to build and package PowerShell from specified form and branch -# Script is intented to use in Docker containers -# Ensure PowerShell is available in the provided image - -param ( - # Set default location to where VSTS cloned the repository locally. - [string] $location = $env:BUILD_REPOSITORY_LOCALPATH, - - # Destination location of the package on docker host - [Parameter(Mandatory, ParameterSetName = 'packageSigned')] - [Parameter(Mandatory, ParameterSetName = 'IncludeSymbols')] - [Parameter(Mandatory, ParameterSetName = 'Build')] - [string] $destination = '/mnt', - - [Parameter(Mandatory, ParameterSetName = 'packageSigned')] - [Parameter(Mandatory, ParameterSetName = 'IncludeSymbols')] - [Parameter(Mandatory, ParameterSetName = 'Build')] - [ValidatePattern("^v\d+\.\d+\.\d+(-\w+(\.\d{1,2})?)?$")] - [ValidateNotNullOrEmpty()] - [string]$ReleaseTag, - - [Parameter(ParameterSetName = 'packageSigned')] - [Parameter(ParameterSetName = 'IncludeSymbols')] - [Parameter(ParameterSetName = 'Build')] - [ValidateSet("zip", "tar")] - [string[]]$ExtraPackage, - - [Parameter(Mandatory, ParameterSetName = 'Bootstrap')] - [switch] $BootStrap, - - [Parameter(Mandatory, ParameterSetName = 'IncludeSymbols')] - [Parameter(Mandatory, ParameterSetName = 'Build')] - [switch] $Build, - - [Parameter(Mandatory, ParameterSetName = 'IncludeSymbols')] - [switch] $Symbols, - - [Parameter(Mandatory, ParameterSetName = 'packageSigned')] - [ValidatePattern("-signed.zip$")] - [string]$BuildZip, - - [Parameter(Mandatory, ParameterSetName = 'packageSigned')] - [Parameter(Mandatory, ParameterSetName = 'IncludeSymbols')] - [Parameter(Mandatory, ParameterSetName = 'Build')] - [ValidateSet('osx-x64', 'osx-arm64')] - [string]$Runtime, - - [string]$ArtifactName = 'result', - - [switch]$SkipReleaseChecks -) - -$repoRoot = $location - -if ($Build -or $PSCmdlet.ParameterSetName -eq 'packageSigned') { - $releaseTagParam = @{} - if ($ReleaseTag) { - $releaseTagParam['ReleaseTag'] = $ReleaseTag - - #Remove the initial 'v' from the ReleaseTag - $version = $ReleaseTag -replace '^v' - $semVersion = [System.Management.Automation.SemanticVersion] $version - - $metadata = Get-Content "$location/tools/metadata.json" -Raw | ConvertFrom-Json - - $LTS = $metadata.LTSRelease.Package - - Write-Verbose -Verbose -Message "LTS is set to: $LTS" - } -} - -Push-Location -try { - $pspackageParams = @{ SkipReleaseChecks = $SkipReleaseChecks; MacOSRuntime = $Runtime } - Write-Verbose -Message "Init..." -Verbose - Set-Location $repoRoot - Import-Module "$repoRoot/build.psm1" - Import-Module "$repoRoot/tools/packaging" - Sync-PSTags -AddRemoteIfMissing - - if ($BootStrap) { - Start-PSBootstrap -Package - } - - if ($PSCmdlet.ParameterSetName -eq 'packageSigned') { - Write-Verbose "Expanding signed build $BuildZip ..." -Verbose - Expand-PSSignedBuild -BuildZip $BuildZip - - Remove-Item -Path $BuildZip - - Start-PSPackage @pspackageParams @releaseTagParam - switch ($ExtraPackage) { - "tar" { Start-PSPackage -Type tar @pspackageParams @releaseTagParam } - } - - if ($LTS) { - Start-PSPackage @pspackageParams @releaseTagParam -LTS - switch ($ExtraPackage) { - "tar" { Start-PSPackage -Type tar @pspackageParams @releaseTagParam -LTS } - } - } - } - - if ($Build) { - if ($Symbols) { - Start-PSBuild -Clean -Configuration 'Release' -NoPSModuleRestore @releaseTagParam -Runtime $Runtime - $pspackageParams['Type']='zip' - $pspackageParams['IncludeSymbols']=$Symbols.IsPresent - Write-Verbose "Starting powershell packaging(zip)..." -Verbose - Start-PSPackage @pspackageParams @releaseTagParam - } else { - Start-PSBuild -Configuration 'Release' -PSModuleRestore @releaseTagParam -Runtime $Runtime - Start-PSPackage @pspackageParams @releaseTagParam - switch ($ExtraPackage) { - "tar" { Start-PSPackage -Type tar @pspackageParams @releaseTagParam } - } - - if ($LTS) { - Start-PSPackage @releaseTagParam -LTS - switch ($ExtraPackage) { - "tar" { Start-PSPackage -Type tar @pspackageParams @releaseTagParam -LTS } - } - } - } - } -} finally { - Pop-Location -} - -if ($Build -or $PSCmdlet.ParameterSetName -eq 'packageSigned') { - $macPackages = Get-ChildItem "$repoRoot/powershell*" -Include *.pkg, *.tar.gz, *.zip - foreach ($macPackage in $macPackages) { - $filePath = $macPackage.FullName - $extension = (Split-Path -Extension -Path $filePath).Replace('.', '') - Write-Verbose "Copying $filePath to $destination" -Verbose - Write-Host "##vso[artifact.upload containerfolder=$ArtifactName;artifactname=$ArtifactName]$filePath" - Write-Host "##vso[task.setvariable variable=Package-$extension]$filePath" - Copy-Item -Path $filePath -Destination $destination -Force - } -} diff --git a/tools/releaseBuild/macOS/PowerShellPackageVsts.sh b/tools/releaseBuild/macOS/PowerShellPackageVsts.sh deleted file mode 100644 index b7bfa7315d8..00000000000 --- a/tools/releaseBuild/macOS/PowerShellPackageVsts.sh +++ /dev/null @@ -1 +0,0 @@ -pwsh -command ".\PowerShellPackageVsts.ps1 $*" diff --git a/tools/releaseBuild/macOS/createPowerShell.sh b/tools/releaseBuild/macOS/createPowerShell.sh deleted file mode 100644 index 5b0b681716c..00000000000 --- a/tools/releaseBuild/macOS/createPowerShell.sh +++ /dev/null @@ -1,8 +0,0 @@ -# print version for diags -sw_vers -productVersion - -# create folder -sudo mkdir /PowerShell - -# make the current user the owner -sudo chown $USER /PowerShell diff --git a/tools/releaseBuild/packagesigning.xml b/tools/releaseBuild/packagesigning.xml deleted file mode 100644 index a243e5fbd98..00000000000 --- a/tools/releaseBuild/packagesigning.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="utf-8" ?> -<!-- template used by generatePackageSigning.ps1 --> -<SignConfigXML> - <job platform="" configuration="" dest="__OUTPATHROOT__\signed" jobname="PowerShell Core Installer" approvers="vigarg;gstolt"> - </job> -</SignConfigXML> diff --git a/tools/releaseBuild/signing.xml b/tools/releaseBuild/signing.xml deleted file mode 100644 index a6b19f6a07a..00000000000 --- a/tools/releaseBuild/signing.xml +++ /dev/null @@ -1,49 +0,0 @@ -<?xml version="1.0" encoding="utf-8" ?> -<SignConfigXML> - <!-- ****Begin**** AuthenticodeFormer and should be StrongName, but we will add this in 6.1.0 ******** --> - <job platform="" configuration="" dest="__OUTPATHROOT__\signed" jobname="PowerShell Core" approvers="vigarg;gstolt"> - <file src="__INPATHROOT__\Microsoft.Management.Infrastructure.CimCmdlets.dll" signType="AuthenticodeFormer" dest="__OUTPATHROOT__\Microsoft.Management.Infrastructure.CimCmdlets.dll" /> - <file src="__INPATHROOT__\Microsoft.PowerShell.Commands.Diagnostics.dll" signType="AuthenticodeFormer" dest="__OUTPATHROOT__\Microsoft.PowerShell.Commands.Diagnostics.dll" /> - <file src="__INPATHROOT__\Microsoft.PowerShell.Commands.Management.dll" signType="AuthenticodeFormer" dest="__OUTPATHROOT__\Microsoft.PowerShell.Commands.Management.dll" /> - <file src="__INPATHROOT__\Microsoft.PowerShell.Commands.Utility.dll" signType="AuthenticodeFormer" dest="__OUTPATHROOT__\Microsoft.PowerShell.Commands.Utility.dll" /> - <file src="__INPATHROOT__\Microsoft.PowerShell.ConsoleHost.dll" signType="AuthenticodeFormer" dest="__OUTPATHROOT__\Microsoft.PowerShell.ConsoleHost.dll" /> - <file src="__INPATHROOT__\Microsoft.PowerShell.CoreCLR.Eventing.dll" signType="AuthenticodeFormer" dest="__OUTPATHROOT__\Microsoft.PowerShell.CoreCLR.Eventing.dll" /> - <file src="__INPATHROOT__\Microsoft.PowerShell.GlobalTool.Shim.dll" signType="AuthenticodeFormer" dest="__OUTPATHROOT__\Microsoft.PowerShell.GlobalTool.Shim.dll" /> - <file src="__INPATHROOT__\Microsoft.PowerShell.Security.dll" signType="AuthenticodeFormer" dest="__OUTPATHROOT__\Microsoft.PowerShell.Security.dll" /> - <file src="__INPATHROOT__\Microsoft.WSMan.Management.dll" signType="AuthenticodeFormer" dest="__OUTPATHROOT__\Microsoft.WSMan.Management.dll" /> - <file src="__INPATHROOT__\Microsoft.WSMan.Runtime.dll" signType="AuthenticodeFormer" dest="__OUTPATHROOT__\Microsoft.WSMan.Runtime.dll" /> - <file src="__INPATHROOT__\System.Management.Automation.dll" signType="AuthenticodeFormer" dest="__OUTPATHROOT__\System.Management.Automation.dll" /> - <file src="__INPATHROOT__\pwsh.dll" signType="AuthenticodeFormer" dest="__OUTPATHROOT__\pwsh.dll" /> - - <!-- not actually a code file, don't sign for now - <file src="__INPATHROOT__\Microsoft.PowerShell.SDK.dll" signType="BothDual" dest="__OUTPATHROOT__\Microsoft.PowerShell.SDK.dll" /> - --> - - <!-- ****Begin**** AuthenticodeFormer ************* --> - - <!-- be sure to sign the global tool shim --> - <file src="__INPATHROOT__\Microsoft.PowerShell.GlobalTool.Shim.exe" signType="AuthenticodeFormer" dest="__OUTPATHROOT__\Microsoft.PowerShell.GlobalTool.Shim.exe" /> - <file src="__INPATHROOT__\pwsh.exe" signType="AuthenticodeFormer" dest="__OUTPATHROOT__\pwsh.exe" /> - - - <!-- ****Begin**** AuthenticodeFormer - Authenticode SHA256 ************* --> - <!-- PowerShell script files cannot be dual signed, so we will sign them only with a SHA256 cert --> - - <file src="__INPATHROOT__\Install-PowerShellRemoting.ps1" signType="AuthenticodeFormer" dest="__OUTPATHROOT__\Install-PowerShellRemoting.ps1" /> - <file src="__INPATHROOT__\RegisterManifest.ps1" signType="AuthenticodeFormer" dest="__OUTPATHROOT__\RegisterManifest.ps1" /> - <file src="__INPATHROOT__\InstallPSCorePolicyDefinitions.ps1" signType="AuthenticodeFormer" dest="__OUTPATHROOT__\InstallPSCorePolicyDefinitions.ps1" /> - <file src="__INPATHROOT__\Modules\CimCmdlets\CimCmdlets.psd1" signType="AuthenticodeFormer" dest="__OUTPATHROOT__\Modules\CimCmdlets\CimCmdlets.psd1" /> - <file src="__INPATHROOT__\Modules\Microsoft.PowerShell.Diagnostics\Microsoft.PowerShell.Diagnostics.psd1" signType="AuthenticodeFormer" dest="__OUTPATHROOT__\Modules\Microsoft.PowerShell.Diagnostics\Microsoft.PowerShell.Diagnostics.psd1" /> - <file src="__INPATHROOT__\Modules\Microsoft.PowerShell.Host\Microsoft.PowerShell.Host.psd1" signType="AuthenticodeFormer" dest="__OUTPATHROOT__\Modules\Microsoft.PowerShell.Host\Microsoft.PowerShell.Host.psd1" /> - <file src="__INPATHROOT__\Modules\Microsoft.PowerShell.Management\Microsoft.PowerShell.Management.psd1" signType="AuthenticodeFormer" dest="__OUTPATHROOT__\Modules\Microsoft.PowerShell.Management\Microsoft.PowerShell.Management.psd1" /> - <file src="__INPATHROOT__\Modules\Microsoft.PowerShell.Security\Microsoft.PowerShell.Security.psd1" signType="AuthenticodeFormer" dest="__OUTPATHROOT__\Modules\Microsoft.PowerShell.Security\Microsoft.PowerShell.Security.psd1" /> - <file src="__INPATHROOT__\Modules\Microsoft.PowerShell.Utility\Microsoft.PowerShell.Utility.psd1" signType="AuthenticodeFormer" dest="__OUTPATHROOT__\Modules\Microsoft.PowerShell.Utility\Microsoft.PowerShell.Utility.psd1" /> - <file src="__INPATHROOT__\Modules\Microsoft.WSMan.Management\Microsoft.WSMan.Management.psd1" signType="AuthenticodeFormer" dest="__OUTPATHROOT__\Modules\Microsoft.WSMan.Management\Microsoft.WSMan.Management.psd1" /> - <file src="__INPATHROOT__\Modules\PSDiagnostics\PSDiagnostics.psd1" signType="AuthenticodeFormer" dest="__OUTPATHROOT__\Modules\PSDiagnostics\PSDiagnostics.psd1" /> - <file src="__INPATHROOT__\Modules\PSDiagnostics\PSDiagnostics.psm1" signType="AuthenticodeFormer" dest="__OUTPATHROOT__\Modules\PSDiagnostics\PSDiagnostics.psm1" /> - <file src="__INPATHROOT__\Modules\Microsoft.WSMan.Management\WSMan.format.ps1xml" signType="AuthenticodeFormer" dest="__OUTPATHROOT__\Modules\Microsoft.WSMan.Management\WSMan.format.ps1xml" /> - <file src="__INPATHROOT__\Modules\Microsoft.PowerShell.Diagnostics\Event.format.ps1xml" signType="AuthenticodeFormer" dest="__OUTPATHROOT__\Modules\Microsoft.PowerShell.Diagnostics\Event.format.ps1xml" /> - <file src="__INPATHROOT__\Modules\Microsoft.PowerShell.Diagnostics\GetEvent.types.ps1xml" signType="AuthenticodeFormer" dest="__OUTPATHROOT__\Modules\Microsoft.PowerShell.Diagnostics\GetEvent.types.ps1xml" /> - <file src="__INPATHROOT__\Modules\Microsoft.PowerShell.Diagnostics\Diagnostics.format.ps1xml" signType="AuthenticodeFormer" dest="__OUTPATHROOT__\Modules\Microsoft.PowerShell.Diagnostics\Diagnostics.format.ps1xml" /> - </job> -</SignConfigXML> diff --git a/tools/releaseBuild/updateSigning.ps1 b/tools/releaseBuild/updateSigning.ps1 deleted file mode 100644 index bace3aec2b7..00000000000 --- a/tools/releaseBuild/updateSigning.ps1 +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -param( - [string] $SigningXmlPath = (Join-Path -Path $PSScriptRoot -ChildPath 'signing.xml'), - [switch] $SkipPwshExe -) -# Script for use in VSTS to update signing.xml - -if ($SkipPwshExe) { - ## This is required for fxdependent package as no .exe is generated. - $xmlContent = Get-Content $SigningXmlPath | Where-Object { $_ -notmatch '__INPATHROOT__\\pwsh.exe' } -} else { - ## We skip the global tool shim assembly for regular builds. - $xmlContent = Get-Content $signingXmlPath | Where-Object { $_ -notmatch '__INPATHROOT__\\Microsoft.PowerShell.GlobalTool.Shim.dll' } -} - -# Parse the signing xml -$signingXml = [xml] $xmlContent - -# Get any variables to updating 'signType' in the XML -# Define a varabile named `<signTypeInXml>SignType' in VSTS to updating that signing type -# Example: $env:AuthenticodeSignType='newvalue' -# will cause all files with the 'Authenticode' signtype to be updated with the 'newvalue' signtype -$signTypes = @{} -Get-ChildItem -Path env:/*SignType | ForEach-Object -Process { - $signType = $_.Name.ToUpperInvariant().Replace('SIGNTYPE','') - Write-Host "Found SigningType $signType with value $($_.value)" - $signTypes[$signType] = $_.Value -} - -# examine each job in the xml -$signingXml.SignConfigXML.job | ForEach-Object -Process { - # examine each file in the job - $_.file | ForEach-Object -Process { - # if the sign type is one of the variables we found, update it to the new value - $signType = $_.SignType.ToUpperInvariant() - if($signTypes.ContainsKey($signType)) - { - $newSignType = $signTypes[$signType] - Write-Host "Updating $($_.src) to $newSignType" - $_.signType = $newSignType - } - } -} - -$signingXml.Save($signingXmlPath) diff --git a/tools/releaseBuild/vstsbuild.ps1 b/tools/releaseBuild/vstsbuild.ps1 deleted file mode 100644 index 1c2d740c418..00000000000 --- a/tools/releaseBuild/vstsbuild.ps1 +++ /dev/null @@ -1,120 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -[cmdletbinding(DefaultParameterSetName='Build')] -param( - [Parameter(ParameterSetName='packageSigned')] - [Parameter(ParameterSetName='Build')] - [ValidatePattern("^v\d+\.\d+\.\d+(-\w+(\.\d{1,2})?)?$")] - [string]$ReleaseTag, - - # full paths to files to add to container to run the build - [Parameter(Mandatory,ParameterSetName='packageSigned')] - [string] - $BuildPath, - - [Parameter(Mandatory,ParameterSetName='packageSigned')] - [string] - $SignedFilesPath -) - -DynamicParam { - # Add a dynamic parameter '-Name' which specifies the name of the build to run - - # Get the names of the builds. - $buildJsonPath = (Join-Path -Path $PSScriptRoot -ChildPath 'build.json') - $build = Get-Content -Path $buildJsonPath | ConvertFrom-Json - $names = @($build.Windows.Name) - foreach($name in $build.Linux.Name) - { - $names += $name - } - - # Create the parameter attributs - $ParameterAttr = New-Object "System.Management.Automation.ParameterAttribute" - $ValidateSetAttr = New-Object "System.Management.Automation.ValidateSetAttribute" -ArgumentList $names - $Attributes = New-Object "System.Collections.ObjectModel.Collection``1[System.Attribute]" - $Attributes.Add($ParameterAttr) > $null - $Attributes.Add($ValidateSetAttr) > $null - - # Create the parameter - $Parameter = New-Object "System.Management.Automation.RuntimeDefinedParameter" -ArgumentList ("Name", [string], $Attributes) - $Dict = New-Object "System.Management.Automation.RuntimeDefinedParameterDictionary" - $Dict.Add("Name", $Parameter) > $null - return $Dict -} - -Begin { - $Name = $PSBoundParameters['Name'] -} - -End { - $ErrorActionPreference = 'Stop' - - $additionalFiles = @() - $buildPackageName = $null - # If specified, Add package file to container - if ($BuildPath) - { - Import-Module (Join-Path -Path $PSScriptRoot -ChildPath '..\..\build.psm1') - Import-Module (Join-Path -Path $PSScriptRoot -ChildPath '..\packaging') - - # Use temp as destination if not running in VSTS - $destFolder = $env:temp - if($env:BUILD_STAGINGDIRECTORY) - { - # Use artifact staging if running in VSTS - $destFolder = $env:BUILD_STAGINGDIRECTORY - } - - $BuildPackagePath = New-PSSignedBuildZip -BuildPath $BuildPath -SignedFilesPath $SignedFilesPath -DestinationFolder $destFolder - Write-Verbose -Verbose "New-PSSignedBuildZip returned `$BuildPackagePath as: $BuildPackagePath" - Write-Host "##vso[artifact.upload containerfolder=results;artifactname=results]$BuildPackagePath" - $buildPackageName = Split-Path -Path $BuildPackagePath -Leaf - $additionalFiles += $BuildPackagePath - } - - $psReleaseBranch = 'master' - $psReleaseFork = 'PowerShell' - $location = Join-Path -Path $PSScriptRoot -ChildPath 'PSRelease' - if(Test-Path $location) - { - Remove-Item -Path $location -Recurse -Force - } - - $gitBinFullPath = (Get-Command -Name git).Source - if (-not $gitBinFullPath) - { - throw "Git is required to proceed. Install from 'https://git-scm.com/download/win'" - } - - Write-Verbose "cloning -b $psReleaseBranch --quiet https://github.com/$psReleaseFork/PSRelease.git" -Verbose - & $gitBinFullPath clone -b $psReleaseBranch --quiet https://github.com/$psReleaseFork/PSRelease.git $location - - Push-Location -Path $PWD.Path - - $unresolvedRepoRoot = Join-Path -Path $PSScriptRoot '../..' - $resolvedRepoRoot = (Resolve-Path -Path $unresolvedRepoRoot).ProviderPath - - try - { - Write-Verbose "Starting build at $resolvedRepoRoot ..." -Verbose - Import-Module "$location/vstsBuild" -Force - Import-Module "$location/dockerBasedBuild" -Force - Clear-VstsTaskState - - $buildParameters = @{ - ReleaseTag = $ReleaseTag - BuildPackageName = $buildPackageName - } - - Invoke-Build -RepoPath $resolvedRepoRoot -BuildJsonPath './tools/releaseBuild/build.json' -Name $Name -Parameters $buildParameters -AdditionalFiles $AdditionalFiles - } - catch - { - Write-VstsError -Error $_ - } - finally{ - Write-VstsTaskState - exit 0 - } -} diff --git a/tools/releaseBuild/vstsbuild.sh b/tools/releaseBuild/vstsbuild.sh deleted file mode 100644 index d7d0363745f..00000000000 --- a/tools/releaseBuild/vstsbuild.sh +++ /dev/null @@ -1 +0,0 @@ -pwsh -command ".\vstsbuild.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 9aa74f28531..99edf93c08a 100644 --- a/tools/releaseTools.psm1 +++ b/tools/releaseTools.psm1 @@ -37,12 +37,35 @@ $Script:powershell_team = @( "dependabot-preview[bot]" "dependabot[bot]" "github-actions[bot]" + "Copilot" "Anam Navied" "Andrew Schwartzmeyer" "Jason Helmick" "Patrick Meinecke" "Steven Bucher" "PowerShell Team Bot" + "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. @@ -53,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, @@ -150,13 +168,20 @@ function Get-ChangeLog [Parameter(Mandatory = $true)] [string]$ThisReleaseTag, - [Parameter(Mandatory)] + [Parameter(Mandatory = $false)] [string]$Token, [Parameter()] [switch]$HasCherryPick ) + if(-not $Token) { + $Token = Get-GHDefaultAuthToken + if(-not $Token) { + throw "No GitHub Auth Token provided" + } + } + $tag_hash = git rev-parse "$LastReleaseTag^0" $format = '%H||%P||%aN||%aE||%s' $header = @{"Authorization"="token $Token"} @@ -254,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)) } @@ -281,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-"} @@ -320,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'" } } } } @@ -360,6 +426,29 @@ function Get-ChangeLog Write-Output "[${version}]: https://github.com/PowerShell/PowerShell/compare/${LastReleaseTag}...${ThisReleaseTag}`n" } +function Get-GHDefaultAuthToken { + $IsGHCLIInstalled = $false + if (Get-command -CommandType Application -Name gh -ErrorAction SilentlyContinue) { + $IsGHCLIInstalled = $true + } else { + Write-Error -Message "GitHub CLI is not installed. Please install it from https://cli.github.com/" -ErrorAction Stop + } + + if ($IsGHCLIInstalled) { + try { + $Token = & gh auth token + } catch { + Write-Error -Message "Please login to GitHub CLI using 'gh auth login'" + } + } + + if (-not $Token) { + $Token = Read-Host -Prompt "Enter GitHub Auth Token" + } + + return $Token +} + function PrintChangeLog($clSection, $sectionTitle, [switch] $Compress) { if ($clSection.Count -gt 0) { "### $sectionTitle`n" @@ -395,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 } @@ -836,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